首頁  >  文章  >  go語言怎麼實現並發控制

go語言怎麼實現並發控制

DDD
DDD原創
2023-06-08 14:20:471740瀏覽

go語言實現並發控制的方法:1、WaitGroup,多個goroutine的任務處理存在依賴或拼接關係;2、Channel,可以主動取消goroutine;多groutine中資料傳遞;代替WaitGroup的工作,滿足Context的功能;3、Context,多層級groutine之間的訊號傳播,包括元資料傳播,取消訊號傳播、逾時控制等。

go語言怎麼實現並發控制

本文的操作環境:Windows10系統、go1.20版本、dell g3電腦。

Golang中透過go關鍵字就開啟一個goroutine,因此,在Go中可以輕鬆寫出並發程式碼。但是,如何對這些並發執行的groutines有效地控制?

提到並發控制,很多人可能最先想到的是鎖。 Golang中同樣提供了鎖的相關機制,包括互斥鎖sync.Mutex,和讀寫鎖sync.RWMutex。除了鎖,還有原子操作sync/atomic等。但是,這些機制關注的重點是goroutines的並發資料安全性。而本文想討論的是goroutine的並發行為控制。

在goroutine並發行為控制中,有三種常見的方式,分別是WaitGroup、channel和Context。

WaitGroup

WaitGroup位於sync套件下,它的使用方法如下。

func main() {
  var wg sync.WaitGroup

  wg.Add(2) //添加需要完成的工作量2

  go func() {
    wg.Done() //完成工作量1
    fmt.Println("goroutine 1 完成工作!")
  }()

  go func() {
    wg.Done() //完成工作量1
    fmt.Println("goroutine 2 完成工作!")
  }()

  wg.Wait() //等待工作量2均完成
  fmt.Println("所有的goroutine均已完成工作!")}输出:
//goroutine 2 完成工作!
//goroutine 1 完成工作!
//所有的goroutine均已完成工作!

WaitGroup這種並發控制方式尤其適用於:某任務需要多 goroutine 協同工作,每個 goroutine 只能做該任務的一部分,只有全部的 goroutine 都完成,任務才算是完成。因此,WaitGroup同名字的含義一樣,是一種等待的方式。

但是,在實際的業務中,有這麼一種場景:當滿足某個要求時,需主動的通知某一個 goroutine 結束。例如我們開啟一個後台監控goroutine,當不再需要監控時,就應該通知這個監控 goroutine 結束,不然它會一直空轉,造成洩漏。

Channel

對於上述場景,WaitGroup無能為力。那能想到的最簡單的方法:定義一個全域變量,在其它地方透過修改這個變數來通知,後台goroutine 會不停的檢查這個變量,如果發現變數發生了變化,即自行關閉,但是這個方法未免有些笨拙。這種情況,channel select可派上用場。

func main() {
  exit := make(chan bool)

  go func() {
    for {
      select {
      case <-exit:
        fmt.Println("退出监控")
        return
      default:
        fmt.Println("监控中")
        time.Sleep(2 * time.Second)
      }
    }
  }()

  time.Sleep(5 * time.Second)
  fmt.Println("通知监控退出")
  exit <- true

  //防止main goroutine过早退出
  time.Sleep(5 * time.Second)}输出:
//监控中
//监控中
//监控中
//通知监控退出
//退出监控

這種 channel select 的組合,是比較優雅的通知goroutine 結束的方式。

但是,方案同樣有其限制。試想,如果有多個 goroutine 都需要控制結束怎麼辦?如果這些 goroutine 又衍生了其它更多的goroutine 呢?當然我們可以定義很多 channel 來解決這個問題,但 goroutine 的關係鏈導致這種場景的複雜性。

Context

以上場景常見於CS架構模型下。在Go中,常常為每個client開啟單獨的goroutine(A)來處理它的一系列request,並且往往單一A中也會請求其他服務(啟動另一個goroutine B),B也可能會要求另外的goroutine C,C再將request傳送給例如Databse的server。設想,當client斷開連接,那麼與之相關聯的A、B、C均需要立即退出,系統才可回收A、B、C所佔用的資源。退出A簡單,但是,要如何通知B、C也退出呢?

這個時候,Context就出場了。

func A(ctx context.Context, name string)  {
  go B(ctx ,name) //A调用了B  for {
    select {
    case <-ctx.Done():
      fmt.Println(name, "A退出")
      return
    default:
      fmt.Println(name, "A do something")
      time.Sleep(2 * time.Second)
    }
  }}func B(ctx context.Context, name string)  {
  for {
    select {
    case <-ctx.Done():
      fmt.Println(name, "B退出")
      return
    default:
      fmt.Println(name, "B do something")
      time.Sleep(2 * time.Second)
    }
  }}func main() {
  ctx, cancel := context.WithCancel(context.Background())

  go A(ctx, "【请求1】") //模拟client来了1个连接请求

  time.Sleep(3 * time.Second)
  fmt.Println("client断开连接,通知对应处理client请求的A,B退出")
  cancel() //假设满足某条件client断开了连接,那么就传播取消信号,ctx.Done()中得到取消信号

  time.Sleep(3 * time.Second)}输出:
//【请求1】 A do something
//【请求1】 B do something
//【请求1】 A do something
//【请求1】 B do something
//client断开连接,通知对应处理client请求的A,B退出
//【请求1】 B退出
//【请求1】 A退出

範例中模擬了客戶端來了連接請求,相應開啟Goroutine A進行處理,A同時開啟了B處理,A和B都使用了Context 進行跟踪,當我們使用cancel 函數通知取消時,這2個goroutine 都會被結束。

這就是Context 的控制能力,它就像一個控制器一樣,按下開關後,所有基於這個Context 或衍生的子Context 都會收到通知,這時就可以進行清理操作了,最終釋放goroutine,這就優雅的解決了goroutine 啟動後不可控的問題。

關於Context的詳細用法,不在本文討論範圍。後續會出專門對Context包的講解文章,敬請關注。

總結

本文列舉了三種Golang中並發行為控制模式。模式之間沒有好壞之分,只在於不同的場景用恰當的方案。實際專案中,往往多種方式混合使用。

  • WaitGroup:多個goroutine的任務處理存在依賴或拼接關係。
  • channel select:可以主動取消goroutine;多groutine中資料傳遞;channel可以取代WaitGroup的工作,但會增加程式碼邏輯複雜性;多channel可以滿足Context的功能,同樣,也會讓程式碼邏輯變得複雜。
  • Context:多層級groutine之間的訊號傳播(包含元資料傳播,取消訊號傳播、逾時控制等)。

Golang中透過go關鍵字就開啟一個goroutine,因此,在Go中可以輕鬆寫出並發程式碼。但是,如何對這些並發執行的groutines有效地控制?

提到並發控制,很多人可能最先想到的是鎖。 Golang中同樣提供了鎖的相關機制,包括互斥鎖sync.Mutex,和讀寫鎖sync.RWMutex。除了鎖,還有原子操作sync/atomic等。但是,這些機制關注的重點是goroutines的並發資料安全性。而本文想討論的是goroutine的並發行為控制。

在goroutine並發行為控制中,有三種常見的方式,分別是WaitGroup、channel和Context。

以上是go語言怎麼實現並發控制的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn