站长资源脚本专栏
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 并发实现协程同步的多种解决方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!