偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Uber工程師對(duì)真實(shí)世界并發(fā)問(wèn)題的研究

開(kāi)發(fā) 運(yùn)維
今天談的這一篇呢,是Uber工程師針對(duì)Uber的眾多的Go代碼做的分析。

今天Uber工程師放出一篇論文(A Study of Real-World Data Races in Golang]( https://arxiv.org/abs/2204.00764)),作者是Uber的工程師Milind Chabbi和Murali Krishna Ramanathan,他們負(fù)責(zé)使用Go內(nèi)建的data race detector在Uber內(nèi)的落地,經(jīng)過(guò)6個(gè)多月的研究分析,他們將data race detector成功落地,并基于對(duì)多個(gè)項(xiàng)目的分析,得出了一些有趣的結(jié)論。

我們知道,Go是Uber公司的主打編程語(yǔ)言。他們對(duì)Uber的2100個(gè)不同的微服務(wù),4600萬(wàn)行Go代碼的分析,發(fā)現(xiàn)了超過(guò)2000個(gè)的有數(shù)據(jù)競(jìng)爭(zhēng)的bug, 修復(fù)了其中的1000多個(gè),剩余的正在分析修復(fù)中。

談起真實(shí)世界中的Go并發(fā)Bug,其實(shí)2019年我們?nèi)A人學(xué)者的 Understanding Real-World Concurrency Bugs in Go 論文可以說(shuō)是開(kāi)山之作,首次全面系統(tǒng)地分析了幾個(gè)流行的大型Go項(xiàng)目的并發(fā)bug。今天談的這一篇呢,是Uber工程師針對(duì)Uber的眾多的Go代碼做的分析。我猜他們可能是類似國(guó)內(nèi)工程效能部的同學(xué),所以這篇論文有一半的篇幅介紹Go data race detector是怎么落地的,這個(gè)我們就不詳細(xì)講了,這篇論文的另一半是基于對(duì)data race的分析,羅列出了常見(jiàn)的出現(xiàn)data race的場(chǎng)景,對(duì)我們Gopher同學(xué)來(lái)說(shuō),很有學(xué)習(xí)的意義,所以我晚上好好拜讀了一下這篇論文,做一總結(jié)和摘要。

作為一個(gè)大廠,肯定不止一種開(kāi)發(fā)語(yǔ)言,作者對(duì)Uber線上個(gè)編程語(yǔ)言(go、java、nodejs、python)進(jìn)行分析,可以看到:

  1. 相比較Java, 在Go語(yǔ)言中會(huì)更多的使用并發(fā)處理
  2. 同一個(gè)進(jìn)程中,nodejs平均會(huì)啟動(dòng)16個(gè)線程,python會(huì)啟動(dòng)16-32個(gè)線程,java進(jìn)程一般啟動(dòng)128-1024個(gè)線程,10%的Java程序啟動(dòng)4096個(gè)線程,7%的java程序啟動(dòng)8192個(gè)線程。Go程序一般啟動(dòng)1024-4096個(gè)goroutine,6%的Go程序啟動(dòng)8192個(gè)goroutine(原文是8102,我認(rèn)為是一個(gè)筆誤),最大13萬(wàn)個(gè)。

可以看到Go程序會(huì)比其它語(yǔ)言有更多的并發(fā)單元,更多的并發(fā)單元意味著存在著更多的并發(fā)bug。Uber代碼庫(kù)中都有哪些類的并發(fā)bug呢?

下面的介紹會(huì)很多的使用數(shù)據(jù)競(jìng)爭(zhēng)概念(data race),它是并發(fā)編程中常見(jiàn)的概念,有數(shù)據(jù)競(jìng)爭(zhēng),意味著有多個(gè)并發(fā)單元對(duì)同一個(gè)數(shù)據(jù)資源有并發(fā)的讀寫(xiě),至少有一個(gè)寫(xiě),有可能會(huì)導(dǎo)致并發(fā)問(wèn)題。

透明地引用捕獲 (Transparent Capture-by-Reference)

直接翻譯過(guò)來(lái)你可能覺(jué)得不知所云。Transparent是指沒(méi)有顯示的聲明或者定義,就直接引用某些變量,很容易導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)。通過(guò)例子更容易理解。這是一大類,我們分成小類逐一介紹。

循環(huán)變量的捕獲

不得不說(shuō),這也是我最常犯的錯(cuò)誤。雖然明明知道會(huì)有這樣的問(wèn)題,但是在開(kāi)發(fā)的過(guò)程中,總是無(wú)意的犯這樣的錯(cuò)誤。

for _ , job := range jobs {
go func () {
ProcessJob ( job )
}()
} // end for

比如這個(gè)簡(jiǎn)單的例子,job是索引變量,循環(huán)中啟動(dòng)了一個(gè)goroutine處理這個(gè)job。job變量就透明地被這個(gè)goroutine引用。

循環(huán)變量是唯一的,意味著啟動(dòng)的這個(gè)goroutine,有可能處理的都是同一個(gè)job,而并不是期望的沒(méi)有一個(gè)job。

這個(gè)例子還很明顯,有時(shí)候循環(huán)體內(nèi)特別復(fù)雜,可能并不像這個(gè)例子那么容易發(fā)現(xiàn)。

err變量被捕獲

Go允許返回值賦值給多個(gè)變量,通常其中一個(gè)變量是error。 x, err := m, n 意味著聲明和定義left hand side(LHS)變量,如果變量還沒(méi)有聲明過(guò)的話,那就是定義了一個(gè)新的變量,但是如果變量已聲明過(guò)得話,那就是對(duì)已有變量的重新賦值。

下面這個(gè)例子,y,z的賦值時(shí),會(huì)對(duì)同一個(gè)err進(jìn)行寫(xiě)操作,也可能會(huì)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng),產(chǎn)生并發(fā)問(wèn)題。

x , err := Foo ()
if err != nil {
...
}

go func () {
y , err := Bar ()
if err != nil {
...
}
}()

z , err := Baz ()
if err != nil {
...
}

捕獲命名的返回值

下面這個(gè)例子定義了一個(gè)命名的返回值 result ??梢钥吹?span> ... = result (讀操作)和 return 20 (寫(xiě)操作)有數(shù)據(jù)競(jìng)爭(zhēng)的問(wèn)題,雖然 return 20 你并沒(méi)有看到對(duì)result的賦值。

func NamedReturnCallee () ( result int) {
result =10
if ... {
return // this has the effect of " return 10"
}
go func () {
... = result // read result
}()
return20 // this is equivalent to result =20
}

func Caller () {
retVal := NamedReturnCallee ()
}

defer 也會(huì)有類似的效果,下面這段代碼對(duì)err有數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。

func Redeem ( request Entity ) ( resp Response , err error )
{
defer func () {
resp , err = c . Foo ( request , err )
}()
err = CheckRequest ( request )
... // err check but no return
go func () {
ProcessRequest ( request , err != nil )
}()
return // the defer function runs after here
}

Slice相關(guān)的數(shù)據(jù)競(jìng)爭(zhēng)

下面這個(gè)例子, safeAppend 使用鎖對(duì) myResults 進(jìn)行了保護(hù),但是在每次循環(huán)調(diào)用 (uuid, myResults) 并沒(méi)有讀保護(hù),也會(huì)有競(jìng)爭(zhēng)問(wèn)題,而且不容易發(fā)現(xiàn)。

func ProcessAll ( uuids [] string ) {
var myResults [] string
var mutex sync . Mutex
safeAppend := func ( res string ) {
mutex.Lock ()
myResults = append ( myResults , res )
mutex.Unlock ()
}

for _ , uuid := range uuids {
go func ( id string , results [] string ) {
res := Foo ( id )
safeAppend ( res )
}( uuid , myResults ) // slice read without holding lock
}
...
}

非線程安全的map

這個(gè)很常見(jiàn)了,幾乎每個(gè)Gopher都曾犯過(guò),犯過(guò)才意識(shí)到Go內(nèi)建的map對(duì)象并不是線程安全的,需要加鎖或者使用sync.Map等其它并發(fā)原語(yǔ)。

func processOrders ( uuids [] string ) error {
var errMap = make ( map [ string ] error )
for _ , uuid := range uuids {
go func ( uuid string ) {
orderHandle , err := GetOrder ( uuid )
if err != nil {
? errMap [ uuid ] = err
return
}
...
}( uuid )
return combineErrors ( errMap )
}

傳值和傳引用的誤用

Go標(biāo)準(zhǔn)庫(kù)常見(jiàn)并發(fā)原語(yǔ)不允許在使用后Copy, go vet也能檢查出來(lái)。比如下面的代碼,兩個(gè)goroutine想共享mutex,需要傳遞 &mutex ,而不是 mutex 。

var a int
// CriticalSection receives a copy of mutex .
func CriticalSection ( m sync . Mutex ) {
m.Lock ()
a ++
m.Unlock ()
}
func main () {
mutex := sync . Mutex {}
// passes a copy of m to A .
go CriticalSection ( mutex )
go CriticalSection ( mutex )
}

混用消息傳遞和共享內(nèi)存兩種并發(fā)方式

消息傳遞常用channel。下面的例子中,如果context因?yàn)槌瑫r(shí)或者主動(dòng)cancel被取消的話,Start中的goroutine中的 f.ch <- 1 可能會(huì)被永遠(yuǎn)阻塞,導(dǎo)致goroutine泄露。

func ( f * Future ) Start () {
go func () {
resp , err := f.f () // invoke a registered function
f.response = resp
f.err = err
f.ch <-1 // may block forever !
}()
}
func ( f * Future ) Wait ( ctx context . Context ) error {
select {
case <-f.ch :
return nil
case <- ctx.Done () :
f.err = ErrCancelled
return ErrCancelled
}

并發(fā)測(cè)試

Go的 testing.T.Parallel() 為單元測(cè)試提供了并發(fā)能力,或者開(kāi)發(fā)者自己寫(xiě)一些并發(fā)的測(cè)試程序測(cè)試代碼邏輯,在這些并發(fā)測(cè)試中,也是有可能導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)的。不要以為測(cè)試不會(huì)有數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。

不正確的鎖調(diào)用

為寫(xiě)操作申請(qǐng)讀鎖

下面這個(gè)例子中, g.ready 是寫(xiě)操作,可是這個(gè)函數(shù)調(diào)用的是讀鎖。

func ( g * HealthGate ) updateGate () {
g.mutex.RLock ()
defer g.mutex.RUnlock ()
// ... several read - only operations ...
if ... {
g.ready = true // Concurrent writes .
g.gate.Accept () // More than one Accept () .
}

其它鎖的問(wèn)題

你會(huì)發(fā)現(xiàn),大家經(jīng)常犯的一個(gè)“弱智”的問(wèn)題,就是Mutex只有Lock或者只有Unlock,或者兩個(gè)Lock,這類問(wèn)題本來(lái)你認(rèn)為絕不會(huì)出現(xiàn)的,在現(xiàn)實(shí)中卻經(jīng)常能看到。

還有使用 atomic 進(jìn)行原子寫(xiě),但是卻沒(méi)有原子讀。

我認(rèn)為這里Uber工程師并沒(méi)有全面詳細(xì)的介紹使用鎖常見(jiàn)的一些陷阱,推薦你學(xué)習(xí)極客時(shí)間中的 Go 并發(fā)編程實(shí)戰(zhàn)課 課程,此課程詳細(xì)介紹了每個(gè)并發(fā)原語(yǔ)的陷阱和死鎖情況。

總結(jié)

總結(jié)一下,下表列出了基于語(yǔ)言類型統(tǒng)計(jì)的數(shù)據(jù)競(jìng)爭(zhēng)bug數(shù):

整體來(lái)看,鎖的誤用是最大的數(shù)據(jù)競(jìng)爭(zhēng)的原因。并發(fā)訪問(wèn)slice和map也是很常見(jiàn)的數(shù)據(jù)競(jìng)爭(zhēng)的原因。

責(zé)任編輯:張燕妮 來(lái)源: 鳥(niǎo)窩
相關(guān)推薦

2022-09-13 13:49:05

數(shù)據(jù)庫(kù)隔離

2009-11-25 13:33:39

并發(fā)

2012-02-02 15:57:09

HibernateJava

2016-04-08 14:32:32

全棧工程師世界

2017-02-22 14:30:30

IT存儲(chǔ)工程師公有云

2016-11-04 13:30:07

Python運(yùn)維工程師

2021-07-01 19:31:50

并發(fā)JavaCPU

2021-02-26 13:50:37

Java并發(fā)代碼

2009-09-03 09:01:38

思科CCIE認(rèn)證思科認(rèn)證CCIE

2021-01-20 10:30:04

大數(shù)據(jù)大數(shù)據(jù)開(kāi)發(fā)

2012-10-18 15:10:51

前端工程師面試題WEB開(kāi)發(fā)

2009-10-09 23:03:45

2010-04-12 16:24:15

Oracle表查詢

2015-08-26 14:18:25

Web前端工程師價(jià)值

2009-04-13 11:50:14

經(jīng)驗(yàn)交流職業(yè)分析面試

2009-02-26 10:57:52

CCNA網(wǎng)絡(luò)工程師認(rèn)證考試

2015-09-30 10:25:03

前端工程師

2022-03-14 18:14:17

NetOps網(wǎng)絡(luò)

2015-05-04 13:24:12

工程師OpenStack公有云

2023-10-23 08:12:34

并發(fā)問(wèn)題有鎖和無(wú)鎖
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)