一、介绍
在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)

不同点iatomic.LoadUint32(&i)
获取i的值AX, ""..autotmp_4+24(SP)AX, ""..autotmp_3+20(SP)
""..autotmp_3+20(SP), AX

这个区别就是值传递和地址传递的区别。
直接打印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个。所以误差更小。


海生
104 声望32 粉丝

与黑夜里,追求那一抹萤火。