Go中由WaitGroup引发对内存对齐思考

来源:这里教程网 时间:2026-03-03 16:23:09 作者:

WaitGroup使用大家都会,但是其中是怎么实现的我们也需要知道,这样才能在项目中尽可能的避免由于不正确的使用引发的panic。并且本文也将写一下内存对齐方面做一个解析,喜欢大家喜欢。 WaitGroup介绍 WaitGroup 提供了三个方法:     func (wg *WaitGroup) Add(delta int)     func (wg *WaitGroup) Done()     func (wg *WaitGroup) Wait()   Add,用来设置 WaitGroup 的计数值; Done,用来将 WaitGroup 的计数值减 1,其实就是调用了 Add(-1); Wait,调用这个方法的 goroutine 会一直阻塞,直到 WaitGroup 的计数值变为 0。 例子我就不举了,网上是很多的,下面我们直接进入正题。 解析 type noCopy struct{} type WaitGroup struct {     // 避免复制使用的一个技巧,可以告诉vet工具违反了复制使用的规则 noCopy noCopy // 一个复合值,用来表示waiter数、计数值、信号量 state1 [3]uint32 }// 获取state的地址和信号量的地址func (wg *WaitGroup) state() (statep *uint64, semap *uint32) { if uintptr(unsafe.Pointer(&wg.state1))%8 == 0 { // 如果地址是64bit对齐的,数组前两个元素做state,后一个元素做信号量 return (*uint64)(unsafe.Pointer(&wg.state1)), &wg.state1[2] } else { // 如果地址是 友链交易 32bit对齐的,数组后两个元素用来做state,它可以用来做64bit的原子操作,第一个元素32bit用来做信号量 return (*uint64)(unsafe.Pointer(&wg.state1[1])), &wg.state1[0] } }   这里刚开始,WaitGroup就秀了一把肌肉,让我们看看大牛是怎么写代码的,思考一个原子操作在不同架构平台上是怎么操作的,在看state方法里面为什么要这么做之前,我们先来看看内存对齐。 内存对齐 A memory address a is said to be n-byte aligned when a is a multiple of n (where n is a power of 2). 简而言之,现在的CPU访问内存的时候是一次性访问多个bytes,比如32位架构一次访问4bytes,该处理器只能从地址为4的倍数的内存开始读取数据,所以要求数据在存放的时候首地址的值是4的倍数存放,者就是所谓的内存对齐。 由于找不到Go语言的对齐规则,我对照了一下C语言的内存对齐的规则,可以和Go语言匹配的上,所以先参照下面的规则。 内存对齐遵循下面三个原则: 结构体变量的起始地址能够被其最宽的成员大小整除; 结构体每个成员相对于起始地址的偏移能够被其自身大小整除,如果不能则在前一个成员后面补充字节; 结构体总体大小能够被最宽的成员的大小整除,如不能则在后面补充字节; 通过下面的例子来实操一下内存对齐: 32位架构中,int8占1byte,int32占4bytes,int16占2bytes。 type A struct { a int8 b int32 c int16 } type B struct { a int8 c int16 b int32 } func main() {   fmt.Printf("arrange fields to reduce size:\n"+ "A align: %d, size: %d\n" , unsafe.Alignof(A{}), unsafe.Sizeof(A{}) )   fmt.Printf("arrange fields to reduce size:\n"+ "B align: %d, size: %d\n" , unsafe.Alignof(B{}), unsafe.Sizeof(B{}) ) } //output://arrange fields to reduce size://A align: 4, size: 12//arrange fields to reduce size://B align: 4, size: 8   下面以在32位的架构中运行为例子: 32位架构的系统中默认的对齐大小是4bytes。 假设结构体A中a的起始地址为0x0000,能够被最宽的数据成员大小4bytes(int32)整除,所以从0x0000开始存放占用一个字节即0x00000x0001;b是int32,占4bytes,所以要满足条件2,需要在a后面padding3个byte,从0x0004开始;c是int16,占2bytes故从0x0008开始占用两个字节,即0x00080x0009;此时整个结构体占用的空间是0x0000~0x0009占用10个字节,10%4 != 0, 不满足第三个原则,所以需要在后面补充两个字节,即最后内存对齐后占用的空间是0x0000~0x000B,一共12个字节。

相关推荐