Golang Go语言中 sync.WaitGroup 的疑惑
实在看不懂了,越看越懵,sync.Mutex 无压力看懂,到这个 waitgroup 就跪了,求亲爹指教一下,跪谢
首先 waitgroup 基本每个版本都有改动,每个改动都逃不开几个话题,内存对齐、原子性
先看一下 1.17 的结构
type WaitGroup struct {
noCopy noCopy
state1 [3]uint32
}
func (wg *WaitGroup) state() (statep *uint64, semap *uint32) {
if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2]
} else {
return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0]
}
}
再看一下 1.18 的结构
type WaitGroup struct {
noCopy noCopy
state1 uint64
state2 uint32
}
func (wg *WaitGroup) state() (statep *uint64, semap uint32) {
if unsafe.Alignof(wg.state1) == 8 || uintptr(unsafe.Pointer(&wg.state1))%8 == 0 {
// state1 is 64-bit aligned: nothing to do.
return &wg.state1, &wg.state2
} else {
// state1 is 32-bit aligned but not 64-bit aligned: this means that
// (&state1)+4 is 64-bit aligned.
state := ([3]uint32)(unsafe.Pointer(&wg.state1))
return (*uint64)(unsafe.Pointer(&state[1])), &state[0]
}
}
在 32 位系统下,state 函数会走 else ,让 state1[1]和 state1[2]一起变成 uint64 ,这个 uint64 大小为 8 字节 64 位
waitgroup 废了这么大劲,网上说主要是为了对齐和原子性
1.我不理解如何保证原子性 使用 state1 字段时候,都用了 atmoic ,我理解 atmoic 已经保证了原子性,莫非是 32 位系统读取 64 位 uint64 时候,这个读操作不是原子性的??如果是这样那么我不理解问题 2
2.为什么 state 函数在 32 位上的实现可以保证原子性 假如 uint64 占用了 0~31 和 32~63 两个地址位,那么无论怎么对齐,32 位系统怎么能保证一次性、原子性的把这 64 位地址读出来?
3.为什么 state 函数对 8 取模就能判断是 64 位还是 32 位系统 32 位系统中 WaitGroup 结构体就不能是地址 0x00008 么??这样对 8 取模会判断成是 64 位
4.为啥不直接使用 uint32 ,解决了对齐和原子性问题
虚心求教
Golang Go语言中 sync.WaitGroup 的疑惑
更多关于Golang Go语言中 sync.WaitGroup 的疑惑的实战教程也可以访问 https://www.itying.com/category-94-b0.html
32 位需要两个 CPU 指令来操作一个 64 位的值,所以不是原子的。
我只能回答着一个…
更多关于Golang Go语言中 sync.WaitGroup 的疑惑的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
1 和 2 是同一个问题,Golang 的 atomic 包要求用户自己保证 64 位对齐,具体可以看官方文档
https://pkg.go.dev/sync/atomic#pkg-note-BUG
至于为什么 32 位对齐不行 64 位就可以,只能说是系统提供的 atomic 原语本身决定的。
3 我觉得你是想复杂了,作者的目的不是判断系统是 32 位还是 64 位,他只是想保证从一段连续的 96 位取出来当作
statep 的那 64 位是对齐的就好了。比如说如果是 [0, 96),那就取 [0, 64) 作为 statep ,这一段不管是在 32 位系统还是 64 位系统,都是 64 位对齐的,如果是 [32, 128),那就取 [64, 128) 作为 statep ,理由一样。
4 的话很简单,因为 uint32 不够用。statep 本身就包含了两个 counter ,每个 counter 在 uint32 的范围。atomic 操作 statep 其实是 atomic 操作这两个 counter 。你如果用 uint32 ,那每个 counter 就只有 uint16 的范围了,65536 很容易就超了不满足设计需求。
另外针对 1 2 再补充一句,不必太纠结为什么 32 位对齐不行 64 位对齐就可以,把它当成一个既定特性就好。系统给 Golang 提供的 API 就是这样,Golang 的 atomic 包只是对系统 API 的一系列简单封装,自然也没法儿绕过这个特性。至于各种 32 位系统为什么将 API 设计成这样(原子操作 64 位一定要 64 位对齐),那就得去看设计者的考虑和取舍了。
求教怎么样才能无压力看懂 sync.Mutex
谢谢您了,懂了
在Go语言中,sync.WaitGroup
是一个非常实用的并发控制工具,它主要用于等待一组协程(goroutines)完成它们的任务。这里解答一些常见的关于sync.WaitGroup
的疑惑:
-
如何使用
sync.WaitGroup
?- 首先,你需要创建一个
WaitGroup
实例。 - 使用
Add
方法增加计数器,表示有多少个协程需要等待。 - 在每个协程内部,使用
Done
方法减少计数器。 - 在主协程中,调用
Wait
方法阻塞,直到计数器归零。
- 首先,你需要创建一个
-
Add
方法可以在协程中调用吗?- 不推荐。
Add
方法应该在启动协程之前调用,以避免竞态条件。
- 不推荐。
-
Done
方法调用次数多于Add
方法设置的次数会怎样?WaitGroup
的计数器可以为负,这通常表示编程错误。虽然程序不会崩溃,但可能会导致意外的行为或死锁。
-
WaitGroup
是否线程安全?- 是的,
WaitGroup
是线程安全的,可以在多个协程中安全地使用。
- 是的,
-
WaitGroup
的适用场景?WaitGroup
适用于需要等待一组任务完成的情况,例如,初始化多个资源、并行处理数据集合等。
总之,sync.WaitGroup
是Go语言中处理并发任务等待的一个强大工具,但使用时需要注意避免竞态条件和编程错误。希望这些信息能帮助你更好地理解和使用sync.WaitGroup
!