一、介绍
在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) |
---|---|---|
获取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个。所以误差更小。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。