Go 源碼 atomic-LoadUint32-- 和 Uint32 相關處理方法
一、介紹
在 go 語言中,atomic 包提供和內存處理的原子化操作,在同步程序處理中,經常使用。
LoadUint32() 函數,提供加載原子化內存 * addr,並返回 Uint32 格式。
語法如下:
// LoadUint32 atomically loads *addr.
func LoadUint32(addr *uint32) (val uint32)
在這裏 , addr 是地址,*addr 是地址的指針
備註: (*uint32) 是一個指向 uint32 值的指針. 並且, uint32 是一個 unsigned 32-bit 整數的縮寫, 範圍從 0 到 4294967295.
返回:指針 * addr 的 uint32 格式值
簡單使用如下:
func TestLoadUint32(t *testing.T) {
var i0 uint32 = 0
var i1 uint32 = 1
var i50 uint32 = 50
var i5000 uint32 = 5000
t.Log(atomic.LoadUint32(&i0))
t.Log(atomic.LoadUint32(&i1))
t.Log(atomic.LoadUint32(&i50))
t.Log(atomic.LoadUint32(&i5000))
}
輸出如下:
0
1
50
5000
二、atomic.LoadUint32() 和正常的獲取值有啥區別?
我們寫下以下的代碼,其中各自打印 i,以及用 LoadUint32()
func TestLoadUint32(t *testing.T) {
var i uint32 = 0
print(i)
print(atomic.LoadUint32(&i))
}
查看一下彙編,其中 sync_test.go 問文件名
go tool compile -N -l -S sync_test.go
輸出如下:
其中 print(i) 部分是:
0x003d 00061 (sync_test.go:10) CALL runtime.printlock(SB)
0x0042 00066 (sync_test.go:10) PCDATA $0, $1
0x0042 00066 (sync_test.go:10) MOVQ "".&i+32(SP), AX
0x0047 00071 (sync_test.go:10) PCDATA $0, $0
0x0047 00071 (sync_test.go:10) MOVL (AX), AX
0x0049 00073 (sync_test.go:10) MOVQ AX, ""..autotmp_4+24(SP)
0x004e 00078 (sync_test.go:10) MOVQ AX, (SP)
0x0052 00082 (sync_test.go:10) CALL runtime.printuint(SB)
0x0057 00087 (sync_test.go:10) CALL runtime.printunlock(SB)
其中 print(atomic.LoadUint32(&i)) 部分是:
0x005c 00092 (sync_test.go:11) PCDATA $0, $1
0x005c 00092 (sync_test.go:11) PCDATA $1, $0
0x005c 00092 (sync_test.go:11) MOVQ "".&i+32(SP), AX
0x0061 00097 (sync_test.go:11) PCDATA $0, $0
0x0061 00097 (sync_test.go:11) MOVL (AX), AX
0x0063 00099 (sync_test.go:11) MOVL AX, ""..autotmp_3+20(SP)
0x0067 00103 (sync_test.go:11) CALL runtime.printlock(SB)
0x006c 00108 (sync_test.go:11) MOVL ""..autotmp_3+20(SP), AX
0x0070 00112 (sync_test.go:11) MOVQ AX, (SP)
0x0074 00116 (sync_test.go:11) CALL runtime.printuint(SB)
0x0079 00121 (sync_test.go:11) CALL runtime.printunlock(SB)
對比一下我們發現主要的區別是
print(i) 和 atomic.LoadUint32(&i)
這個區別就是值傳遞和地址傳遞的區別。
直接打印 i,只是把值打印出來
atomic.LoadUint32(&i),是把值打印出來,並且再保存一下到原來的地址中。
三、 AddUint32 和正常的 + 號,有啥區別?
1、正常的 + 2 加號增加
func TestAdd(t *testing.T) {
var u uint32
// For 循環10000次
for i := 1; i <= 10000; i++ {
// 加2
go func() {
u += 2
}()
}
// 打印u的值
print(atomic.LoadUint32(&u)) //18580
}
2、原子化 AddUint32 增加 1
func TestAddUint32Uint32(t *testing.T) {
var u uint32
// For 循環10000次
for i := 1; i <= 10000; i++ {
// 加2
go func() {
atomic.AddUint32(&u, 2)
}()
}
// 打印u的值
print(atomic.LoadUint32(&u)) //19984
}
同樣在循環 1w 次的加 1 操作上,從誤差來看,原子化 AddUint32 比正常的 + 2,更接近正確答案,誤差更小。
3、正常的 + 號和 AddUint32 有啥區別
我們打印一下 TestAdd 函數的 u += 2 彙編語言發下如下
0x0000 00000 (sync_test.go:20) PCDATA $0, $1
0x0000 00000 (sync_test.go:20) PCDATA $1, $0
0x0000 00000 (sync_test.go:20) MOVQ "".&u+8(SP), AX
0x0005 00005 (sync_test.go:20) PCDATA $0, $0
0x0005 00005 (sync_test.go:20) MOVL (AX), AX
0x0007 00007 (sync_test.go:20) PCDATA $0, $2
0x0007 00007 (sync_test.go:20) PCDATA $1, $1
0x0007 00007 (sync_test.go:20) MOVQ "".&u+8(SP), CX
0x000c 00012 (sync_test.go:20) ADDL $2, AX
0x000f 00015 (sync_test.go:20) PCDATA $0, $0
0x000f 00015 (sync_test.go:20) MOVL AX, (CX)
然後再打印一下 AddUint32
0x0000 00000 (sync_test.go:14) PCDATA $0, $1
0x0000 00000 (sync_test.go:14) PCDATA $1, $1
0x0000 00000 (sync_test.go:14) MOVQ "".&u+8(SP), AX
0x0005 00005 (sync_test.go:14) MOVL $2, CX
0x000a 00010 (sync_test.go:14) PCDATA $0, $0
0x000a 00010 (sync_test.go:14) LOCK
0x000b 00011 (sync_test.go:14) XADDL CX, (AX)
我們發現差別是
正常的 + 加操作,其實可以分爲三個操作
賦值一個寄存器,MOVQ
然後再加2,ADDL
再賦值給u,MOVL
而 AddUint32 操作只有一個原子化的命令
XADDL
把 3 個操作和爲 1 個。所以誤差更小。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/qQZubqjcnj1sXGZ6wvbNMQ