為什么會(huì)有atomic.LoadInt32
前些天我們聊了 Golang 內(nèi)存對(duì)齊[1]的話題,后來(lái)我突然想到另一個(gè)問(wèn)題:為什么會(huì)有 atomic.LoadInt32[2]?可能你覺(jué)得思維太跳躍了,容我慢慢道來(lái):首先,有 atomic.LoadInt64[3] 很正常,因?yàn)閷?duì)一個(gè) int64 來(lái)說(shuō),它的大小是 8 個(gè)字節(jié),如果是 32 位平臺(tái)的話(字長(zhǎng) 4 字節(jié)),CPU 一次最多操作 4 個(gè)字節(jié),需要兩次才能拿到全部數(shù)據(jù),所以封裝一個(gè) atomic.LoadInt64 來(lái)實(shí)現(xiàn)原子操作;但是,對(duì)一個(gè) int32 數(shù)據(jù)來(lái)說(shuō),它的大小是 4 字節(jié),不管是 32 位平臺(tái)(字長(zhǎng) 4 字節(jié)),還是 64 位平臺(tái)(字長(zhǎng) 8 字節(jié)),CPU 應(yīng)該都可以保證一次操作拿到數(shù)據(jù),換句話說(shuō),如果讀取一個(gè) int32 數(shù)據(jù),那么本身就應(yīng)該是原子的,可是為什么會(huì)有 atomic.LoadInt32,這不是脫了褲子放屁么?
有病沒(méi)病走兩步,讓我們寫一段代碼來(lái)驗(yàn)證一下:
package?main
import?"sync/atomic"
var?v?=?int32(0)
func?main()?{
?var?x?int32
?x?=?v?//?main.go:9
?_?=?x
?x?=?atomic.LoadInt32(&v)?//?main.go:11
?_?=?x
}
通過(guò)「go tool compile」運(yùn)行代碼,拿到對(duì)應(yīng)的匯編結(jié)果:
shell>?go?tool?compile?-N?-l?-S?main.go
0x0016?00022?(main.go:9)????????MOVL????"".v(SB),?AX
0x001c?00028?(main.go:9)????????MOVL????AX,?"".t+4(SP)
0x0020?00032?(main.go:11)???????MOVL????"".v(SB),?AX
0x0026?00038?(main.go:11)???????MOVL????AX,?"".t+4(SP)
不管是「x = v」還是「x = atomic.LoadInt32(&v)」,對(duì)應(yīng)的匯編結(jié)果一摸一樣。問(wèn)題越來(lái)越有趣了,讓我們看看是否能從 sync/atomic[4] 的源代碼中找到答案:
Golang 代碼中只有函數(shù)聲明,實(shí)際上是使用匯編實(shí)現(xiàn)的:
//?doc.go
func?LoadInt32(addr?*int32)?(val?int32)
//?asm.s
TEXT?·LoadInt32(SB),NOSPLIT,$0
?JMP?runtime∕internal∕atomic·Load(SB)
順著路徑,跳轉(zhuǎn)到 runtime/internal/atomic[5],會(huì)發(fā)現(xiàn)每個(gè)平臺(tái)都有獨(dú)立的 Load 實(shí)現(xiàn):
在 amd64 平臺(tái),Load 是用 Golang 實(shí)現(xiàn)的,等價(jià)于直接讀?。?/p>
func?Load(ptr?*uint32)?uint32?{
?return?*ptr
}
在 arm64 平臺(tái),Load 是用匯編實(shí)現(xiàn)的,并不是簡(jiǎn)單的一次操作:
TEXT?·Load(SB),NOSPLIT,$0-12
?MOVD?ptr+0(FP),?R0
?LDARW?(R0),?R0
?MOVW?R0,?ret+8(FP)
?RET
如上可見(jiàn),atomic.LoadInt32 之所以存在,是因?yàn)槟承┢脚_(tái)存在特殊性,所以我們需要封裝一個(gè)統(tǒng)一的操作,如此更有利于我們寫出平臺(tái)無(wú)關(guān)的代碼。
本文僅討論了 atomic 的原子性[6],實(shí)際上它還保證了可見(jiàn)性[7],有序性[8],有興趣的朋友可以搜索內(nèi)存屏障相關(guān)內(nèi)容,這是一個(gè)很復(fù)雜的主題,我就不獻(xiàn)丑了,推薦閱讀:Golang Memory Model[9]。
參考資料
Golang 內(nèi)存對(duì)齊: https://blog.huoding.com/2021/09/29/951
[2]atomic.LoadInt32: https://pkg.go.dev/github.com/hslam/atomic#LoadInt32
[3]atomic.LoadInt64: https://pkg.go.dev/github.com/hslam/atomic#LoadInt64
[4]sync/atomic: https://github.com/golang/go/tree/master/src/sync/atomic
[5]runtime/internal/atomic: https://github.com/golang/go/tree/master/src/runtime/internal/atomic
[6]原子性: https://www.1024cores.net/home/lock-free-algorithms/so-what-is-a-memory-model-and-how-to-cook-it
[7]可見(jiàn)性: https://www.1024cores.net/home/lock-free-algorithms/so-what-is-a-memory-model-and-how-to-cook-it/visibility
[8]有序性: https://www.1024cores.net/home/lock-free-algorithms/so-what-is-a-memory-model-and-how-to-cook-it/ordering
[9]Golang Memory Model: https://www.jianshu.com/p/1596e1d7c126
推薦閱讀
