Golang中 WaitGroup的实现原理是什么

发布时间:2021-06-24 17:27:08 作者:Leah
来源:亿速云 阅读:492

Golang中 WaitGroup的实现原理是什么

在Go语言中,sync.WaitGroup 是一个常用的同步原语,用于等待一组 goroutine 完成任务。WaitGroup 的主要作用是让主 goroutine 等待其他 goroutine 执行完毕后再继续执行。本文将深入探讨 WaitGroup 的实现原理,帮助读者更好地理解其工作机制。

1. WaitGroup 的基本用法

在深入探讨 WaitGroup 的实现原理之前,我们先来看一下它的基本用法。

package main

import (
	"fmt"
	"sync"
	"time"
)

func worker(id int, wg *sync.WaitGroup) {
	defer wg.Done()
	fmt.Printf("Worker %d starting\n", id)
	time.Sleep(time.Second)
	fmt.Printf("Worker %d done\n", id)
}

func main() {
	var wg sync.WaitGroup

	for i := 1; i <= 5; i++ {
		wg.Add(1)
		go worker(i, &wg)
	}

	wg.Wait()
	fmt.Println("All workers done")
}

在这个例子中,我们创建了5个 worker goroutine,每个 worker 在执行完任务后会调用 wg.Done() 来通知 WaitGroup 任务已完成。主 goroutine 通过 wg.Wait() 等待所有 worker 完成任务后再继续执行。

2. WaitGroup 的结构

WaitGroup 的定义如下:

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 do not ensure it. So we allocate 12 bytes and then use
	// the aligned 8 bytes in them as state, and the other 4 as storage
	// for the sema.
	state1 [3]uint32
}

WaitGroup 的结构非常简单,主要包含一个 noCopy 字段和一个 state1 数组。noCopy 是一个空结构体,用于防止 WaitGroup 被复制。state1 是一个长度为3的 uint32 数组,用于存储 WaitGroup 的状态信息。

2.1 state1 的作用

state1 数组的前两个元素用于存储 WaitGroup 的状态信息,第三个元素用于存储信号量(semaphore)。具体来说:

2.2 计数器与等待者数量

WaitGroup 的核心功能是通过计数器(counter)和等待者数量(waiter count)来实现的。

3. WaitGroup 的实现原理

WaitGroup 的实现原理主要依赖于原子操作和信号量。下面我们将详细分析 AddDoneWait 方法的实现。

3.1 Add 方法的实现

Add 方法用于增加或减少计数器的值。其实现如下:

func (wg *WaitGroup) Add(delta int) {
	statep, semap := wg.state()
	state := atomic.AddUint64(statep, uint64(delta)<<32
	v := int32(state >> 32)
	w := uint32(state)
	if v < 0 {
		panic("sync: negative WaitGroup counter")
	}
	if w != 0 && delta > 0 && v == int32(delta) {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	if v > 0 || w == 0 {
		return
	}
	if *statep != state {
		panic("sync: WaitGroup misuse: Add called concurrently with Wait")
	}
	*statep = 0
	for ; w != 0; w-- {
		runtime_Semrelease(semap, false, 0)
	}
}

Add 方法的主要步骤如下:

  1. 通过 wg.state() 获取 statepsemap,分别指向 state1 数组中的状态和信号量。
  2. 使用原子操作 atomic.AddUint64 增加计数器的值,并更新状态。
  3. 检查计数器的值是否小于0,如果是则抛出异常。
  4. 检查是否有等待者且 delta > 0v == int32(delta),如果是则抛出异常。
  5. 如果计数器大于0或没有等待者,则直接返回。
  6. 如果 statep 的值与 state 不一致,则抛出异常。
  7. statep 的值重置为0,并释放所有等待者。

3.2 Done 方法的实现

Done 方法实际上是 Add(-1) 的简写形式。其实现如下:

func (wg *WaitGroup) Done() {
	wg.Add(-1)
}

Done 方法只是简单地调用了 Add(-1),将计数器减1。

3.3 Wait 方法的实现

Wait 方法用于等待计数器变为0。其实现如下:

func (wg *WaitGroup) Wait() {
	statep, semap := wg.state()
	for {
		state := atomic.LoadUint64(statep)
		v := int32(state >> 32)
		w := uint32(state)
		if v == 0 {
			return
		}
		if atomic.CompareAndSwapUint64(statep, state, state+1) {
			runtime_Semacquire(semap)
			if *statep != 0 {
				panic("sync: WaitGroup is reused before previous Wait has returned")
			}
			return
		}
	}
}

Wait 方法的主要步骤如下:

  1. 通过 wg.state() 获取 statepsemap,分别指向 state1 数组中的状态和信号量。
  2. 使用 atomic.LoadUint64 加载当前状态。
  3. 检查计数器是否为0,如果是则直接返回。
  4. 使用 atomic.CompareAndSwapUint64 尝试增加等待者数量。
  5. 如果成功增加等待者数量,则调用 runtime_Semacquire 等待信号量。
  6. 如果 statep 的值不为0,则抛出异常。
  7. 返回。

4. WaitGroup 的并发安全性

WaitGroup 的设计目标是支持并发安全的使用。通过原子操作和信号量,WaitGroup 能够在多个 goroutine 之间安全地同步任务。

4.1 原子操作

WaitGroup 使用原子操作来保证计数器和等待者数量的更新是线程安全的。atomic.AddUint64atomic.CompareAndSwapUint64 等原子操作确保了在并发环境下,状态更新不会出现竞态条件。

4.2 信号量

WaitGroup 使用信号量来实现等待机制。当计数器变为0时,WaitGroup 会释放所有等待的 goroutine。信号量的实现依赖于操作系统的底层机制,确保等待和唤醒操作是线程安全的。

5. WaitGroup 的误用与注意事项

虽然 WaitGroup 是一个强大的同步工具,但在使用过程中也需要注意一些常见的误用情况。

5.1 负计数器

如果在 Add 方法中传入的 delta 导致计数器变为负数,WaitGroup 会抛出异常。因此,在使用 Add 方法时,需要确保传入的 delta 不会导致计数器变为负数。

5.2 并发调用 Add 和 Wait

如果在 Wait 方法调用期间并发调用 Add 方法,可能会导致 WaitGroup 的状态不一致,从而引发异常。因此,在使用 WaitGroup 时,应确保 AddWait 方法的调用是顺序的。

5.3 重用 WaitGroup

Wait 方法返回之前,如果尝试重用 WaitGroup,可能会导致状态不一致,从而引发异常。因此,在使用 WaitGroup 时,应确保在 Wait 方法返回之前不要重用 WaitGroup

6. 总结

sync.WaitGroup 是 Go 语言中用于同步多个 goroutine 的重要工具。通过计数器、等待者数量和信号量的组合,WaitGroup 能够高效地实现 goroutine 的同步。理解 WaitGroup 的实现原理,不仅有助于更好地使用它,还能帮助我们在编写并发程序时避免常见的错误。

在实际开发中,WaitGroup 通常用于等待一组 goroutine 完成任务后再继续执行主逻辑。通过合理使用 AddDoneWait 方法,我们可以轻松实现复杂的并发控制逻辑。

希望本文能够帮助读者深入理解 WaitGroup 的实现原理,并在实际项目中灵活运用。

推荐阅读:
  1. Golang WaitGroup源码分析
  2. 怎么关闭golang协程

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

golang waitgroup atomic

上一篇:小程序中怎么实现瀑布流布局

下一篇:nginx中怎么实现一个事件模块

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》