为什么你不应该接受有 race 的代码
在任何語言的并發(fā)編程場景中,都有 race 問題,現(xiàn)代語言為了解決 race 問題有兩種思路,一種是像 rust 那樣的通過所有權(quán)+Sync/Send 限制用戶盡量無法寫出帶 race 的代碼;一種是像 Go 這樣,通過 race detector 在測試期間檢查數(shù)據(jù)競爭問題。
Go 的 race detector 設(shè)計決定了其無法在線上環(huán)境開啟,而很多公司的項目上線前其實是沒有 race test 的環(huán)節(jié)的,這就導(dǎo)致了一些 Gopher 認(rèn)為我寫出 race 的代碼也沒關(guān)系,因為可以“最終一致”。
在 Go 官方的?《The Go Memory Model》[1]?一文中已經(jīng)駁斥過這些觀點了,比如有這么一個例子:
var?a?string var?done?boolfunc?setup()?{a?=?"hello,?world"done?=?true }func?main()?{go?setup()for?!done?{}print(a) }全局變量 a 的寫入和 done 的修改從代碼層面講存在先后關(guān)系,但因為你沒有在代碼中使用任何同步工具(哪怕是 atomic 操作),所以這樣的代碼你沒法保證在 for 循環(huán)檢查到 done 變成 true 之后,一定能打印出 "hello, world"。
這里還只是因為 CPU 和編譯器的亂序執(zhí)行導(dǎo)致了問題,按照這些程序員的想法,我不關(guān)心順序,我只關(guān)注最終一致,反正 done 肯定能被改成 true,a 也一定能被修改成 hello, world。
這種想法也是有問題的,多核環(huán)境下 CPU 有多級緩存,如果連 atomic 都不使用,那么在多個核心間也不一定會同步你這種寫行為。最差的情況下,在一個核心里寫了 done = true,另一個核心一直讀不到,這也是正常的(并不好復(fù)現(xiàn))。說不定什么時候硬件為了優(yōu)化就真的會給你這么干啊。
官方給出的這個例子,我們在 build/run 的時候加上簡單的 -race flag,也是可以及時發(fā)現(xiàn)問題的:
~/test?git:master?????go?run?-race?./r.go ================== WARNING:?DATA?RACE Write?at?0x0000011506a1?by?goroutine?6:main.setup()/Users/xargin/test/r.go:8?+0x73Previous?read?at?0x0000011506a1?by?main?goroutine:main.main()/Users/xargin/test/r.go:13?+0x3eGoroutine?6?(running)?created?at:main.main()/Users/xargin/test/r.go:12?+0x32 ================== ================== WARNING:?DATA?RACE Read?at?0x000001121bf0?by?main?goroutine:main.main()/Users/xargin/test/r.go:15?+0x53Previous?write?at?0x000001121bf0?by?goroutine?6:main.setup()/Users/xargin/test/r.go:7?+0x30Goroutine?6?(finished)?created?at:main.main()/Users/xargin/test/r.go:12?+0x32 ================== hello,?worldFound?2?data?race(s) exit?status?66發(fā)現(xiàn)了有 data race 就一定要去解決,如果允許這樣的代碼進(jìn)入到你的工程里,那么這樣的錯誤就會越來越多,當(dāng)未來某個時刻你需要去查并發(fā)導(dǎo)致的 bug 了,那成百上千的 race 輸出都是技術(shù)債,到時候再來還就晚了。
如果可以的話,race test 也最好集成在你的 CI 環(huán)境中,初級工程師最擅長的就是這個:
如果你允許有 race 的代碼進(jìn)入主分支,日積月累會有更多的 race 通過拷貝擴(kuò)散。
當(dāng)你花了一個星期還沒有辦法定位出線上偶發(fā)的并發(fā)問題的時候,可能就只能提桶跑路了。
要寫并發(fā)相關(guān)的代碼,還是要好好學(xué)習(xí)一下并發(fā)知識的。這里可以推薦兩本相關(guān)的書:《Shared Memory Synchronization》 和 《perfbook》。
[1]
《The Go Memory Model》:?https://go.dev/ref/mem
總結(jié)
以上是生活随笔為你收集整理的为什么你不应该接受有 race 的代码的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Go大型项目实战:项目结构、配置管理、A
- 下一篇: 你应该如何学习?