当前位置:首页 >> 脚本专栏

Go 并发实现协程同步的多种解决方法

go 简洁的并发

多核处理器越来越普及。有没有一种简单的办法,能够让我们写的软件释放多核的威力?是有的。随着Golang, Erlang, Scala等为并发设计的程序语言的兴起,新的并发模式逐渐清晰。正如过程式编程和面向对象一样,一个好的编程模式有一个极其简洁的内核,还有在此之上丰富的外延。可以解决现实世界中各种各样的问题。本文以GO语言为例,解释其中内核、外延。

前言

Java 中有一系列的线程同步的方法,go 里面有 goroutine(协程),先看下下面的代码执行的结果是什么呢?

 package main
 import (
   "fmt"
)
 func main() {
  go func() {
    fmt.Println("Goroutine 1")
  }()
  go func() {
    fmt.Println("Goroutine 2")
  }()
}

执行以上代码很可能看不到输出。

因为有可能这两个协程还没得到执行,主协程就已经结束了,而主协程结束时会结束所有其他协程,所以导致代码运行的结果什么都没有。

估计不少新接触 go 的童鞋都会对此郁闷"color: #ff0000">Sleep 一段时间

在 main 方法退出之前 sleep 一段时间就可能会出现结果了,如下代码:

 package main
 import (
   "fmt"
  "time"
 )
 func main() {
   go func() {
    fmt.Println("Goroutine 1")
  }()
  go func() {
    fmt.Println("Goroutine 2")
  }()
  time.Sleep(time.Second * 1) // 睡眠1秒,等待上面两个协程结束
}

这两个简单的协程执行消耗的时间很短的,所以你会发现现在就有结果出现了。

Goroutine 1
Goroutine 2

为什么上面我要说 “可能会出现” ?

因为 sleep 这个时间目前是设置的 1s,如果我这两个协程里面执行了很复杂的逻辑操作(时间大于 1s),那么就会发现依旧也是无结果打印出来的。

那么就可以发现这种方式得到问题所在了:我们无法确定需要睡眠多久

上面那种方式有问题,go 里面其实也可以用管道来实现同步的。

管道实现同步

那么用管道怎么实现同步呢?show code:

 package main
 import (
   "fmt"
 )
 func main() {
   ch := make(chan struct{})
  count := 2 // count 表示活动的协程个数
  go func() {
    fmt.Println("Goroutine 1")
    ch <- struct{}{} // 协程结束,发出信号
  }()
  go func() {
    fmt.Println("Goroutine 2")
    ch <- struct{}{} // 协程结束,发出信号
  }()
  for range ch {
    // 每次从ch中接收数据,表明一个活动的协程结束
    count--
    // 当所有活动的协程都结束时,关闭管道
    if count == 0 {
      close(ch)
    }
  }
}

这种方式是一种比较完美的解决方案, goroutine / channel 它们也是在 go 里面经常搭配在一起的一对。

sync.WaitGroup

其实 go 里面也提供了更简单的方式 —— 使用 sync.WaitGroup。

WaitGroup 顾名思义,就是用来等待一组操作完成的。WaitGroup 内部实现了一个计数器,用来记录未完成的操作个数,它提供了三个方法:

  • Add() 用来添加计数
  • Done() 用来在操作结束时调用,使计数减一
  • Wait() 用来等待所有的操作结束,即计数变为 0,该函数会在计数不为 0 时等待,在计数为 0 时立即返回

继续 show code:

package main
 import (
   "fmt"
  "sync"
 )
 func main() {
  var wg sync.WaitGroup
  wg.Add(2) // 因为有两个动作,所以增加2个计数
  go func() {
    fmt.Println("Goroutine 1")
    wg.Done() // 操作完成,减少一个计数
  }()
  go func() {
    fmt.Println("Goroutine 2")
    wg.Done() // 操作完成,减少一个计数
  }()
  wg.Wait() // 等待,直到计数为0
}

你会发现也是可以看到运行结果的,是不是发现这种方式是很简单的。

总结

以上所述是小编给大家介绍的Go 并发实现协程同步的多种解决方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!