Golang 协程同步机制详解
admin
2024-02-01 03:24:20

本文介绍协同同步机制,基于示例和源码详解其实现原理,并总结应用同步机制的最佳实践。

问题引入

为了等待协程完成,我们可以使用空结构体通道,并在操作最后给通道发送值。

	ch := make(chan struct{})for i := 0; i < n; n++ {go func() {// do somethingch <- struct{}{}}()}for i := 0; i < n; n++ {<-ch}

这种策略可以实现,但不建议使用。这在语义上也不正确,使用通道作为工具发送空数据,我们的使用场景是同步而不是通信。这就需要引入sync.WaitGroup 数据结构,专门用于同步场景。

同步机制

sync.WaitGroup 数据结构包括主状态,称为计数器,标识等待元素数量,源码如下:

// A WaitGroup waits for a collection of goroutines to finish.
// The main goroutine calls Add to set the number of
// goroutines to wait for. Then each of the goroutines
// runs and calls Done when finished. At the same time,
// Wait can be used to block until all goroutines have finished.
//
// A WaitGroup must not be copied after first use.
//
// In the terminology of the Go memory model, a call to Done
// “synchronizes before” the return of any Wait call that it unblocks.
type WaitGroup struct {noCopy noCopy// 64-bit value: high 32 bits are counter, low 32 bits are waiter count.// 64-bit atomic operations require 64-bit alignment, but 32-bit// compilers only guarantee that 64-bit fields are 32-bit aligned.// For this reason on 32 bit architectures we need to check in state()// if state1 is aligned or not, and dynamically "swap" the field order if// needed.state1 uint64state2 uint32
}

noCopy字段防止按值拷贝,还有状态字段。另外还提供三个方法:

  • Add

使用指定值改变计数器的值,也可以是负数。如果计数器变为零,应用会panics.

  • Done
// Done decrements the WaitGroup counter by one.
func (wg *WaitGroup) Done() {wg.Add(-1)
}

可以但Done是Add(-1)的简化形式。通常在协程完成工作时调用。

  • Wait

该操作阻塞当前协程直到计数器为零。

案例分析

下面使用WaitGroup重构上面示例,会让代码更清晰、易读:

	func main() {wg := sync.WaitGroup{}wg.Add(10)for i := 1; i <= 10; i++ {go func(a int) {for i := 1; i <= 10; i++ {fmt.Printf("%dx%d=%d\n", a, i, a*i)}wg.Done()}(i)}wg.Wait()}
}

循环开始前,我们设置WaitGroup计数器为协程的数量,这我们在启动之前就已知道。然后每完成一个使用Done方法减少计数器。

如果事前不知道总数量呢,Add方法可以在开始执行协程之前增加1,代码如下:

func main() {wg := sync.WaitGroup{}for i := 1; rand.Intn(10) != 0; i++ {wg.Add(1)go func(a int) {for i := 1; i <= 10; i++ {fmt.Printf("%dx%d=%d\n", a, i, a*i)}wg.Done()}(i)}wg.Wait()
}

上面示例事前不任务数量不确定,因此在每次任务之前调用Add(1),其他过程与上面一致。

常见的错误是add方法在协程内部,这通常会导致提前退出,而不执行任何gor协程。

引用源码中的注释作为最佳实践:

A WaitGroup waits for a collection of goroutines to finish.The main goroutine calls Add to set the number of goroutines to wait for. 
Then each of the goroutines runs and calls Done when finished. 
At the same time, Wait can be used to block until all goroutines have finished.

简单翻译下:

WaitGroup实现等待一组协程完成机制。

  • 主协程调用Add方法,并使之需要等待协程的数量
  • 每个协程运行,结束时调用Done方法
  • 同时,Wait方法阻塞,直到所有协程都完成

相关内容

热门资讯

孙杨无证驾驶事件始末 孙杨无证... 孙杨豪华座驾   交警出示的事故认定书     孙杨   谁能想到孙杨将交通法规当儿戏...
汉中市中心医院护士刘秋兰和邓琼... 汉中市中心医院护士刘秋兰和邓琼月照片资料上央视新闻联播了 在汉中市中心医院发生男子持菜刀砍患者的生死...
最新或2023(历届)浙江省最...  浙江省政府日前决定,再次调整全省最低工资标准,将最低月工资标准的最高一档调整为1860元。  从最...
最新或2023(历届)安徽工程... 安徽工程大学是一所以工为主的省属多科性高等院校和安徽省重点建设院校,坐落在国家级开放城市芜湖。学校办...
最新或2023(历届)安徽理工... 安徽理工大学是安徽省重点建设的特色高水平大学,是国家“中西部高等教育振兴计划”—中西部高校基础能力建...