博客 / 詳情

返回

腦抽研究生Go併發-3-拓展併發原語-信號量、SingleFlightv和 循環柵欄、分組操作(ErrGroup)等

擴展併發原語

信號量(Semaphore/Weighted)

圖片

​ 信號量(Semaphore/Weighted)是用來控制多個 goroutine 同時訪問多個資源的併發原語

  • 初始化信號量:設定初始的資源的數量。
  • P 操作:將信號量的計數值減去 1,如果新值已經為負,那麼調用者會被阻塞並加入到等待隊列中。否則,調用者會繼續執行,並且獲得一個資源。
  • V 操作:將信號量的計數值加 1,如果先前的計數值為負,就説明有等待的 P 操作的調用者。它會從等待隊列中取出一個等待的調用者,喚醒它,讓它繼續執行。

​ 一般用信號量保護一組資源,比如數據庫連接池、一組客户端的連接、幾個打印機資源,等等

  1. Acquire 方法:相當於 P 操作,你可以一次獲取多個資源,如果沒有足夠多的資源,調用者就會被阻塞。它的第一個參數是 Context,這就意味着,你可以通過 Context 增加超時或者 cancel 的機制。如果是正常獲取了資源,就返回 nil;否則,就返回 ctx.Err(),信號量不改變。
  2. Release 方法:相當於 V 操作,可以將 n 個資源釋放,返還給信號量。
  3. TryAcquire 方法:嘗試獲取 n 個資源,但是它不會阻塞,要麼成功獲取 n 個資源,返回 true,要麼一個也不獲取,返回 false。

tips:如果在實際應用中,你想等所有的 Worker 都執行完,就可以獲取最大計數值的信號量。

使用信號量的常見錯誤

  • 請求了資源,但是忘記釋放它;
  • 釋放了從未請求的資源;
  • 長時間持有一個資源,即使不需要它;
  • 不持有一個資源,卻直接使用它。

SingleFlight(冪等性) 和 CyclicBarrier(循環柵欄/組裝工廠)

圖片

SingleFlight :將併發請求合併成一個請求,以減少對下層服務的壓力

CyclicBarrier :可重用的柵欄併發原語,用來控制一組請求同時執行的數據結構

請求合併 SingleFlight

作用:在處理多個 goroutine 同時調用同一個函數的時候,只讓一個 goroutine 去調用這個函數,等到這個 goroutine 返回結果的時候,再把結果返回給這幾個同時調用的 goroutine,這樣可以減少併發調用的數量

應用:面對秒殺等大併發請求的場景,而且這些請求都是讀請求時,可以把這些請求合併為一個請求

  • Do:這個方法執行一個函數,並返回函數執行的結果。你需要提供一個 key,對於同一個 key,在同一時間只有一個在執行,同一個 key 併發的請求會等待。第一個執行的請求返回的結果,就是它的返回結果。函數 fn 是一個無參的函數,返回一個結果或者 error,而 Do 方法會返回函數執行的結果或者是 error,shared 會指示 v 是否返回給多個請求。
  • DoChan:類似 Do 方法,只不過是返回一個 chan,等 fn 函數執行完,產生了結果以後,就能從這個 chan 中接收這個結果。
  • Forget:告訴 Group 忘記這個 key。這樣一來,之後這個 key 請求會執行 f,而不是等待前一個未完成的 fn 函數的結果。

緩存穿透:查詢一個數據庫裏壓根就沒有的數據

  • 結局辦法:緩存空對象、布隆過濾器

緩存雪崩:大量不同的 Key 在同一時間集體失效,導致流量直擊數據庫

  • 解決辦法:高可用緩存集羣、過期時間打散、服務降級與限流

緩存擊穿:某一個熱點 Key 過期,導致海量併發請求同時“重建”這個 Key 的緩存

  • 解決辦法:互斥鎖、熱點數據永不過期

groupcache

一致性哈希singleflight 思想

  • 只適用於那些希望簡化部署、並且主要緩存需求是防止熱點數據擊穿的特定場景

循環柵欄 CyclicBarrier

CyclicBarrier 更適合用在“固定數量的 goroutine 等待同一個執行點”的場景中,而且在放行 goroutine 之後,CyclicBarrier 可以重複利用

WaitGroup 更適合用在“一個 goroutine 等待一組 goroutine 到達同一個執行點”的場景中,或者是不需要重用的場景中

兩個初始化方法:

  • New 方法,它只需要一個參數,來指定循環柵欄參與者的數量
  • NewWithAction,它額外提供一個函數,可以在每一次到達執行點的時候執行一次。具體的時間點是在最後一個參與者到達之後,但是其它的參與者還未被放行之前

併發趣題:一氧化二氫(水)製造工廠

分組操作

圖片

分組執行一批相同的或類似的任務

ErrGroup

將一個大的任務拆成幾個小任務併發執行

  • WithContext 方法:返回一個 Group 實例,同時還會返回一個使用 context.WithCancel(ctx) 生成的新 Context。一旦有一個子任務返回錯誤,或者是 Wait 調用返回,這個新 Context 就會被 cancel
  • Go 方法:傳入的子任務函數 f 是類型為 func() error 的函數,如果任務執行成功,就返回 nil,否則就返回 error,並且會 cancel 那個新的 Context
  • Wait 方法:類似 WaitGroup

擴展庫:bilibili/errgroup、neilotoole/errgroup、facebookgo/errgroup

gollback

  • All 方法:等待所有的異步函數(AsyncFunc)都執行完才返回,而且返回結果的順序和傳入的函數的順序保持一致。第一個返回參數是子任務的執行結果,第二個參數是子任務執行時的錯誤信息
  • Race 方法:只要一個異步函數執行沒有錯誤,就立馬返回,而不會返回所有的子任務信息。如果所有的子任務都沒有成功,就會返回最後一個 error 信息
  • Retry 方法:執行一個子任務。如果子任務執行失敗,它會嘗試一定的次數,如果一直不成功 ,就會返回失敗錯誤

Hunch

  • All 方法:它會傳入一組可執行的函數(子任務),返回子任務的執行結果。和 gollback 的 All 方法不一樣的是,一旦一個子任務出現錯誤,它就會返回錯誤信息,執行結果(第一個返回參數)為 nil。
  • Take 方法:可以指定 num 參數,只要有 num 個子任務正常執行完沒有錯誤,這個方法就會返回這幾個子任務的結果。一旦一個子任務出現錯誤,它就會返回錯誤信息,執行結果(第一個返回參數)為 nil。
  • Last 方法:只返回最後 num 個正常執行的、沒有錯誤的子任務的結果。一旦一個子任務出現錯誤,它就會返回錯誤信息,執行結果(第一個返回參數)為 nil
  • Retry 方法:和gollback 的 Retry 方法的功能一樣
  • Waterfall 方法:子任務都是串行執行的,前一個子任務的執行結果會被當作參數傳給下一個子任務

schedgroup

和時間相關的處理一組 goroutine 的併發原語

  • Delay 和 Schedule方法:都是用來指定在某個時間或者之後執行一個函數;Delay 處理相對時間,而 Schedule 處理絕對時間
  • Wait 方法:阻塞調用者,直到之前安排的所有子任務都執行完才返回

    ​ 注意:

    • 如果調用了 Wait 方法,你就不能再調用它的 Delay 和 Schedule 方法,否則會 panic
    • Wait 方法只能調用一次,如果多次調用的話,就會 panic
user avatar anygraphanywhere 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.