6

32位系统下,Go标准库中atomic原子操作int64有崩溃bug

 3 years ago
source link: https://www.pengrl.com/p/21030/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

32位系统下,Go标准库中atomic原子操作int64有崩溃bug

发表于 2021-03-12 | 分类于 Go
| 字数统计: 714

下面这个demo,在32位系统(我测试的运行环境:32位arm linux)会崩溃。

package main

import (
"sync/atomic"
)

type Foo struct {
a int64
b int32
c int64
}

func main() {
var f Foo
atomic.AddInt64(&f.a, 1) // 这里不会崩溃
atomic.AddInt64(&f.c, 1) // 这里会崩溃
}

崩溃信息如下:

panic: unaligned 64-bit atomic operation

goroutine 1 [running]:
runtime/internal/atomic.panicUnaligned()
/usr/local/go/src/runtime/internal/atomic/unaligned.go:8 +0x24
runtime/internal/atomic.Xadd64(0x1416084, 0x1, 0x0, 0x75fd8, 0x14000e0)
/usr/local/go/src/runtime/internal/atomic/asm_arm.s:233 +0x14
main.main()
/tmp/test.go:16 +0x3c

在Go源码go/src/sync/atomic/doc.go的注释中,有如下描述:

// BUG(rsc): On x86-32, the 64-bit functions use instructions unavailable before the Pentium MMX.
//
// On non-Linux ARM, the 64-bit functions use instructions unavailable before the ARMv6k core.
//
// On ARM, x86-32, and 32-bit MIPS,
// it is the caller's responsibility to arrange for 64-bit
// alignment of 64-bit words accessed atomically. The first word in a
// variable or in an allocated struct, array, or slice can be relied upon to be
// 64-bit aligned.

大致意思是,在一些32位的环境(包括x86和arm),标准库sync/atomic中操作int64的函数存在bug,调用者需自行保证,被操作的int64是按64位对齐的。。否则给你来个panic。惊不惊喜意不意外。。

这里简单说一下64位对齐是啥意思,拿上面那个demo来说:

type Foo struct {
a int64 // 位置从0开始,满足64位对齐
b int32
c int64 // 位置`size(a)+sizeof(b)=96`,不是64的倍数,就不满足了
}

所以,atomic.AddInt64(&f.a, 1)不会崩溃,atomic.AddInt64(&f.c, 1)会崩溃。

值得一提的有几点:

  1. 64位系统原子操作int64没这个问题
  2. 32位系统原子操作int32也没问题
  3. 32位系统原子操作int64有问题,注意,是原子操作有问题,并不是说int64不能用
  4. uint64和int64是一样,也即这个问题只关心整型位数,不关心符号
  5. 以上说的原子操作,特指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/


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK