Go标准库源码分析: atomic.AddInt64

atomic.AddInt64

介绍

原理

源码

看不到源码解释个勾八原理

源码里只有函数doc, 但是没有函数实现, 但是有一段注释

// AddInt64 atomically adds delta to *addr and returns the new value.
// Consider using the more ergonomic and less error-prone [Int64.Add] instead
// (particularly if you target 32-bit platforms; see the bugs section).

介绍了他的功能是原子性的对地址所指的数字 + delta, 需要注意一个问题, 在32位的平台上不应该使用, 会存在bug

在全局搜索过后, 一段特别的注释引起了我的注意

//go:linkname abigen_sync_atomic_AddInt64 sync/atomic.AddInt64

这条指令告诉编译器,虽然sync/atomic.AddInt64函数定义在sync/atomic包中,但是可以通过abigen_sync_atomic_AddInt64这个别名在其他包中被直接调用,就好像它定义在那个包内一样。

好了, 我们已经找到了实际对应的源码位置, 但是奇怪的事情出现了, 此处依然没有实现

//go:linkname abigen_sync_atomic_AddInt64 sync/atomic.AddInt64
func abigen_sync_atomic_AddInt64(addr *int64, delta int64) (new int64)

在同级目录下, 存在这么一个文件

这就是他的实现源码了, 为了不同的平台的适配, 底层的实现使用了汇编, 在最后编译时在链接起来.

分析

函数签名

func abigen_sync_atomic_AddInt64(addr *int64, delta int64) (new int64)

栈帧布局(不太准确, 先忽略)

+----------------+ 
| addr           |
+----------------+
| delta          | 
+----------------+
| 返回值 (new)    |
+----------------+ 

变量对应

  • addr: +0(FP)
  • delta: +8(FP)
  • new: +16(FP)

代码解释

TEXT    sync∕atomic·AddInt64(SB), NOSPLIT|NOFRAME, $0-24
    GO_ARGS
    MOVQ    $__tsan_go_atomic64_fetch_add(SB), AX
    CALL    racecallatomic<>(SB)
    MOVQ    add+8(FP), AX   // convert fetch_add to add_fetch
    ADDQ    AX, ret+16(FP)
    RET
  1. 载入函数__tsan_go_atomic64_fetch_add到寄存器AX中

  2. 执行函数__tsan_go_atomic64_fetch_add, 这一步执行的是fetch_add

    在并发编程中,fetch_addadd_fetch是两种常见的原子操作,用于实现对共享变量的原子加操作。它们的区别在于操作的顺序不同。

    1. fetch_addfetch_add操作首先读取共享变量的当前值,然后将指定的值加到该变量上,并返回变量之前的值。换句话说,fetch_add的顺序是先读取再相加。
    2. add_fetch:与fetch_add相反,add_fetch操作首先将指定的值加到共享变量上,然后返回变量的新值。换句话说,add_fetch的顺序是先相加再返回。

    举个简单例子,假设共享变量的初始值为0,执行以下操作:

    • fetch_add(3):首先读取变量的当前值为0,然后将3加到变量上,最后返回之前的值0。
    • add_fetch(3):首先将3加到变量上,变量的新值为3,然后返回新值3。
  3. 载入delta, 存放进AX寄存器, 需要注意的是此时的ret+16(FP)存放的是__tsan_go_atomic64_fetch_add的结果, 是未执行加操作前的数值, 在外面在执行一遍加法, 保证一致, 函数结束.

<span style="font-weight: bold;" class="bold">汇编分析</span>

image

我们需要注意看黄色部分的上半边内容

  1. 将 0x3f (63) 载入CX寄存器
  2. ((20240329223156-gclhnmm 'XADD')) 进行原子性的加法, 并将使用加法之前的(AX)写入CX
  3. 将CX结果移入0x8(SP)

但是到这里会有问题, 因为此时返回的是one增加之前的值, 而我是需要的是加完之后的值

我们接着看后半部分

  1. MOVQ 0x8(SP), AX 将返回的结果写入AX寄存器
  2. ADDQ $0x3f, AX 执行加法, 此时得到的才是真正的返回值

也就是说, 这里执行了一个懒加载, 即在实际使用时, 才去计算了真实的返回值.

未优化汇编

image

我们从LEAQ开始看, 能够发现, 0x3f又出现了一次

LEA

https://blog.csdn.net/Chauncyxu/article/details/121890457

加载有效地址(load effective address)指令 leaq 实际上是 movq 指令的变形。

它的指令形式是从内存读数据到寄存器,但实际上它根本就没有引用内存。

第一个操作数 S看上去是一个内存引用,但该指令并不是从指定的位置读入数据,而是将有效地址写入到目的操作数 D。这条指令可以为后面的内存引用产生指针。
目的操作数 D 必须是一个寄存器。
另外,它还可以简洁地描述普通的算术操作。例如,如果寄存器 %rdx 的值为 x,那么指令
leaq 7(%rdx, %rdx,4), %rax 将设置寄存器 %rax 的值为 5x+7。

他所做的事情就是将DX的值 + 63, 然后将结果写回CX

最后将CX的结果写回(AX), (AX)就是实际的返回值即AddInt64的正确返回值

注意

为什么要有Lock?

参考 https://stackoverflow.com/questions/30130752/assembly-does-xadd-instruction-need-lock

如果没有Lock, XADDQ依然可以保证原子性, 但是只能保证在单个core上的原子性, 无法提供全局保证.

PS

__tsan_go_atomic64_fetch_add 函数是 Go 语言运行时在使用数据竞态检测(ThreadSanitizer,简称 TSan)时的内部函数。它的实现细节通常是隐藏的,因为这个函数是由运行时的系统库提供的,不是由 Go 语言本身直接实现的。TSan 是一个用于检测多线程程序中数据竞态的工具,它会在运行时拦截所有的内存操作以检测潜在的数据竞态问题。

在大多数平台, 都是通过LOCK+ XADD来实现的.

atomic.AddInt64的使用还是比较简单的, 只需要传入一个指针, 同时指定delta就可以

atomic.AddInt64(&i, 64)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,240评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,328评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,182评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,121评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,135评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,093评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,013评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,854评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,295评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,513评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,398评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,989评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,636评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,657评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容