下面这个demo,在32位系统(我测试的运行环境:32位arm linux)会崩溃。
1 | package main |
崩溃信息如下:
1 | panic: unaligned 64-bit atomic operation |
在Go源码go/src/sync/atomic/doc.go
的注释中,有如下描述:
1 | // BUG(rsc): On x86-32, the 64-bit functions use instructions unavailable before the Pentium MMX. |
大致意思是,在一些32位的环境(包括x86和arm),标准库sync/atomic
中操作int64的函数存在bug,调用者需自行保证,被操作的int64是按64位对齐的。。否则给你来个panic。惊不惊喜意不意外。。
这里简单说一下64位对齐是啥意思,拿上面那个demo来说:
1 | type Foo struct { |
所以,atomic.AddInt64(&f.a, 1)
不会崩溃,atomic.AddInt64(&f.c, 1)
会崩溃。
值得一提的有几点:
- 64位系统原子操作int64没这个问题
- 32位系统原子操作int32也没问题
- 32位系统原子操作int64有问题,注意,是原子操作有问题,并不是说int64不能用
- uint64和int64是一样,也即这个问题只关心整型位数,不关心符号
- 以上说的原子操作,特指Go标准库
sync/atomic
中的函数
解决方法有两种:
一种是保证结构体中的需要使用atomic的int64按64位对齐,比如最简单的就是把这些变量的声明写在结构体的最前面。同时,还需要保证这种结构体被其他结构体嵌套时,也要64位对齐。缺点是编写和维护代码的心智负担比较大。
另一种就是把int64变量的原子操作改成mutex互斥锁保护。缺点是mutex比atomic的开销大。
我自己的更佳方案:
在我自己的Go基础库naza(https://github.com/q191201771/naza)中,增加对64位原子整型的封装,让它按运行系统的类型(32/64)做条件编译,64位系统内部使用Go标准库atomic中的原子操作函数实现,32位则内部退化成使用mutex实现。
本文完,作者yoko,尊重劳动人民成果,转载请注明原文出处: https://pengrl.com/p/21030/