日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
Uber工程師對(duì)真實(shí)世界并發(fā)問(wèn)題的研究

今天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é)論。

讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來(lái)自于我們對(duì)這個(gè)行業(yè)的熱愛(ài)。我們立志把好的技術(shù)通過(guò)有效、簡(jiǎn)單的方式提供給客戶,將通過(guò)不懈努力成為客戶在信息化領(lǐng)域值得信任、有價(jià)值的長(zhǎng)期合作伙伴,公司提供的服務(wù)項(xiàng)目有:申請(qǐng)域名、虛擬空間、營(yíng)銷軟件、網(wǎng)站建設(shè)、湖州網(wǎng)站維護(hù)、網(wǎng)站推廣。

我們知道,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ō)是開山之作,首次全面系統(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è)大廠,肯定不止一種開發(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ā)的讀寫,至少有一個(gè)寫,有可能會(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)題,但是在開發(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)行寫操作,也可能會(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 ??梢钥吹?nbsp;... = result (讀操作)和 return 20 (寫操作)有數(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ā)能力,或者開發(fā)者自己寫一些并發(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)用

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

下面這個(gè)例子中, g.ready 是寫操作,可是這個(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)行原子寫,但是卻沒(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)的原因。


本文標(biāo)題:Uber工程師對(duì)真實(shí)世界并發(fā)問(wèn)題的研究
本文來(lái)源:http://www.dlmjj.cn/article/dhpgjpg.html