成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

從 Bug 中學習:六大開源項目告訴你 Go 并發編程的那些坑

新聞 前端
并發編程中,Go不僅僅支持傳統的通過共享內存的方式來通信,更推崇通過channel來傳遞消息,這種新的并發編程模型會出現不同于以往的bug。

 并發編程中,go不僅僅支持傳統的通過共享內存的方式來通信,更推崇通過channel來傳遞消息,這種新的并發編程模型會出現不同于以往的bug。從bug中學習,《Understanding Real-World Concurrency Bugs in Go》這篇paper在分析了六大開源項目并發相關的bug之后,為我們總結了go并發編程中常見的坑。別往坑里跳,編程更美妙。

在 go 中,創建 goroutine 非常簡單,在函數調用前加 go 關鍵字,這個函數的調用就在一個單獨的 goroutine 中執行了;go 支持匿名函數,讓創建 goroutine 的操作更加簡潔。另外,在并發編程模型上,go 不僅僅支持傳統的通過共享內存的方式來通信,更推崇通過 channel 來傳遞消息:

Do not communicate by sharing memory; instead, share memory by communicating.

這種新的并發編程模型會帶來新類型的 bug,從 bug 中學習,《Understanding Real-World Concurrency Bugs in Go》這篇 paper 在 Docker、Kubernetes、etcd、gRPC、CockroachDB、BoltDB 六大開源項目的 commit log 中搜索"race"、"deadlock"、"synchronization"、"concurrency"、"lock"、"mutex"、"atomic"、"compete"、"context"、"once"、"goroutine leak"等關鍵字,找出了這六大項目中并發相關的 bug,然后歸類這些 bug,總結出了 go 并發編程中常見的一些坑。通過學習這些坑,可以讓我們在以后的項目里防范類似的錯誤,或者遇到類似問題的時候可以幫助指導快速定位排查。

unbuffered channel 由于 receiver 退出導致 sender 側 block

如下面一個 bug 的例子:

  1. func finishReq(timeout time.Duration) ob { 
  2.     ch := make(chan ob) 
  3.     go func() { 
  4.         result := fn() 
  5.         ch <- result // block 
  6.     }() 
  7.     select { 
  8.     case result = <-ch: 
  9.         return result 
  10.     case <-time.After(timeout): 
  11.         return nil 
  12.     } 

本意是想調用 fn()時加上超時的功能,如果 fn()在超時時間沒有返回,則返回 nil。但是當超時發生的時候,針對代碼中第二行創建的 ch 來說,由于已經沒有 receiver 了,第 5 行將會被 block 住,導致這個 goroutine 永遠不會退出。

If the capacity is zero or absent, the channel is unbuffered and communication succeeds only when both a sender and receiver are ready. Otherwise, the channel is buffered and communication succeeds without blocking if the buffer is not full (sends) or not empty (receives).

這個 bug 的修復方式也是非常的簡單,把 unbuffered channel 修改成 buffered channel。

  1. func finishReq(timeout time.Duration) ob { 
  2.     ch := make(chan ob, 1
  3.     go func() { 
  4.         result := fn() 
  5.         ch <- result // block 
  6.     }() 
  7.     select { 
  8.     case result = <-ch: 
  9.         return result 
  10.     case <-time.After(timeout): 
  11.         return nil 
  12.     } 

思考:在上面的例子中,雖然這樣不會 block 了,但是 channel 一直沒有被關閉,channel 保持不關閉是否會導致資源的泄漏呢?

WaitGroup 誤用導致阻塞

下面是一個 WaitGroup 誤用導致阻塞的一個 bug 的例子: https:// github.com/moby/moby/pu ll/25384

  1. var group sync.WaitGroup 
  2. group.Add(len(pm.plugins)) 
  3. for _, p := range pm.plugins { 
  4.     go func(p *plugin) { 
  5.         defer group.Done() 
  6.     }(p) 
  7.     group.Wait() 

當 len(pm.plugins)大于等于 2 時,第 7 行將會被卡住,因為這個時候只啟動了一個異步的 goroutine,group.Done()只會被調用一次,group.Wait()將會永久阻塞。修復如下:

  1. var group sync.WaitGroup 
  2. group.Add(len(pm.plugins)) 
  3. for _, p := range pm.plugins { 
  4.     go func(p *plugin) { 
  5.         defer group.Done() 
  6.     }(p) 
  7. group.Wait() 

context 誤用導致資源泄漏

如下面的代碼所示:

  1. hctx, hcancel := context.WithCancel(ctx) 
  2. if timeout > 0 { 
  3.     hctx, hcancel = context.WithTimeout(ctx, timeout) 

第一行 context.WithCancel(ctx)有可能會創建一個 goroutine,來等待 ctx 是否 Done,如果 parent 的 ctx.Done()的話,cancel 掉 child 的 context。也就是說 hcancel 綁定了一定的資源,不能直接覆蓋。

Canceling this context releases resources associated with it, so code should call cancel as soon as the operations running in this Context complete.

這個 bug 的修復方式是:

  1. var hctx context.Context 
  2. var hcancel context.CancelFunc 
  3. if timeout > 0 { 
  4.     hctx, hcancel = context.WithTimeout(ctx, timeout) 
  5. else { 
  6.     hctx, hcancel = context.WithCancel(ctx) 

或者

  1. hctx, hcancel := context.WithCancel(ctx) 
  2. if timeout > 0 { 
  3.     hcancel.Cancel() 
  4.     hctx, hcancel = context.WithTimeout(ctx, timeout) 

多個 goroutine 同時讀寫共享變量導致的 bug

如下面的例子:

  1. for i := 17; i <= 21; i++ { // write 
  2.     go func() { /* Create a new goroutine */ 
  3.         apiVersion := fmt.Sprintf("v1.%d", i) // read 
  4.     }() 

第二行中的匿名函數形成了一個閉包(closures),在閉包內部可以訪問定義在外面的變量,如上面的例子中,第 1 行在寫 i 這個變量,在第 3 行在讀 i 這個變量。這里的關鍵的問題是對同一個變量的讀寫是在兩個 goroutine 里面同時進行的,因此是不安全的。

Function literals are closures: they may refer to variables defined in a surrounding function. Those variables are then shared between the surrounding function and the function literal, and they survive as long as they are accessible.

可以修改成:

  1. for i := 17; i <= 21; i++ { // write 
  2.     go func(i int) { /* Create a new goroutine */ 
  3.         apiVersion := fmt.Sprintf("v1.%d", i) // read 
  4.     }(i) 

通過 passed by value 的方式規避了并發讀寫的問題。

channel 被關閉多次引發的 bug

https:// github.com/moby/moby/pu ll/24007/files

  1. select { 
  2. case <-c.closed: 
  3. default
  4.     close(c.closed) 

上面這塊代碼可能會被多個 goroutine 同時執行,這段代碼的邏輯是,case 這個分支判斷 closed 這個 channel 是否被關閉了,如果被關閉的話,就什么都不做;如果 closed 沒有被關閉的話,就執行 default 分支關閉這個 channel,多個 goroutine 并發執行的時候,有可能會導致 closed 這個 channel 被關閉多次。

For a channel c, the built-in function close(c) records that no more values will be sent on the channel. It is an error if c is a receive-only channel. Sending to or closing a closed channel causes a run-time panic.

這個 bug 的修復方式是:

  1. Once.Do(func() { 
  2.     close(c.closed) 
  3. }) 

把整個 select 語句塊換成 Once.Do,保證 channel 只關閉一次。

timer 誤用產生的 bug

如下面的例子:

  1. timer := time.NewTimer(0
  2. if dur > 0 { 
  3.     timer = time.NewTimer(dur) 
  4. select { 
  5. case <-timer.C: 
  6. case <-ctx.Done(): 
  7.     return nil 

原意是想 dur 大于 0 的時候,設置 timer 超時時間,但是 timer := time.NewTimer(0)導致 timer.C 立即觸發。修復后:

  1. var timeout <-chan time.Time 
  2. if dur > 0 { 
  3.     timeout = time.NewTimer(dur).C 
  4. select { 
  5. case <-timeout: 
  6. case <-ctx.Done(): 
  7.     return nil 

A nil channel is never ready for communication.

上面的代碼中第一個 case 分支 timeout 有可能是個 nil 的 channel,select 在 nil 的 channel 上,這個分支不會被觸發,因此不會有問題。

讀寫鎖誤用引發的 bug

go 語言中的 RWMutex,write lock 有更高的優先級:

If a goroutine holds a RWMutex for reading and another goroutine might call Lock, no goroutine should expect to be able to acquire a read lock until the initial read lock is released. In particular, this prohibits recursive read locking. This is to ensure that the lock eventually becomes available; a blocked Lock call excludes new readers from acquiring the lock.

如果一個 goroutine 拿到了一個 read lock,然后另外一個 goroutine 調用了 Lock,第一個 goroutine 再調用 read lock 的時候會死鎖,應予以避免。

 

責任編輯:張燕妮 來源: 騰訊技術
相關推薦

2025-03-24 00:25:00

Go語言并發編程

2024-01-07 13:25:32

Go編程代碼

2024-01-15 06:45:29

Go編程代碼

2020-04-28 08:34:08

KubernetesDocker開源工具

2009-12-17 09:52:37

2011-03-11 14:43:41

Qt-QuickQML

2016-10-18 16:10:57

物聯網開源

2010-08-10 13:42:27

Flex開源項目

2010-07-29 16:31:34

Flex開源項目

2025-05-19 09:28:31

2018-04-27 14:40:18

Java語言程序

2020-10-10 17:34:11

大數據IT技術

2018-06-06 00:06:48

開源存儲存儲軟件存儲

2020-04-13 08:00:00

機器人開源自動化工具

2010-08-05 10:38:10

Flex開源項目

2009-06-10 17:13:50

Java開源軟件

2022-10-17 08:07:13

Go 語言并發編程

2020-09-06 10:02:32

項目管理戰略目標CIO

2009-11-02 09:38:07

開源ESB

2010-09-08 11:23:27

點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: av国产精品 | 日韩精品欧美精品 | 亚洲一区成人 | 欧美炮房| 69电影网| 久久久久久国产精品免费免费男同 | 四虎影视一区二区 | 久久久青草婷婷精品综合日韩 | 国产欧美精品区一区二区三区 | 无人区国产成人久久三区 | 在线国产一区 | 成人精品国产免费网站 | 在线小视频 | 男女黄网站 | 国产免费观看一级国产 | 偷拍亚洲色图 | 久久看片 | 国产目拍亚洲精品99久久精品 | 国产又色又爽又黄又免费 | 国产欧美精品 | 中文字幕免费中文 | 国产欧美一级二级三级在线视频 | 欧美国产日韩一区二区三区 | 亚洲区一区二 | 免费在线看黄视频 | 成人激情视频 | 久久草在线视频 | 天天干成人网 | 日韩人体在线 | 国产精品一区二区在线 | 成人免费视频网站 | 国产精品久久久久aaaa九色 | 国产精品久久久久免费 | 国产精品96久久久久久 | 一区二区三区四区在线 | 毛片网站在线观看 | 久久久久无码国产精品一区 | 欧美国产精品一区二区三区 | 中文字幕第90页 | 国产精品久久久久久久久免费高清 | 精品国产乱码久久久久久牛牛 |