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)

1KH2QA

這個區別就是值傳遞和地址傳遞的區別。
直接打印 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