<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      深入理解Go語言中的sync.Cond

      1. 簡介

      本文將介紹 Go 語言中的 sync.Cond 并發原語,包括 sync.Cond的基本使用方法、實現原理、使用注意事項以及常見的使用使用場景。能夠更好地理解和應用 Cond 來實現 goroutine 之間的同步。

      2. 基本使用

      2.1 定義

      sync.Cond是Go語言標準庫中的一個類型,代表條件變量。條件變量是用于多個goroutine之間進行同步和互斥的一種機制。sync.Cond可以用于等待和通知goroutine,以便它們可以在特定條件下等待或繼續執行。

      2.2 方法說明

      sync.Cond的定義如下,提供了Wait ,Singal,Broadcast以及NewCond方法

      type Cond struct {
         noCopy noCopy
         // L is held while observing or changing the condition
         L Locker
      
         notify  notifyList
         checker copyChecker
      }
      
      func NewCond(l Locker) *Cond {}
      func (c *Cond) Wait() {}
      func (c *Cond) Signal() {}
      func (c *Cond) Broadcast() {}
      
      • NewCond方法: 提供創建Cond實例的方法
      • Wait方法: 使當前線程進入阻塞狀態,等待其他協程喚醒
      • Singal方法: 喚醒一個等待該條件變量的線程,如果沒有線程在等待,則該方法會立即返回。
      • Broadcast方法: 喚醒所有等待該條件變量的線程,如果沒有線程在等待,則該方法會立即返回。

      2.3 使用方式

      當使用sync.Cond時,通常需要以下幾個步驟:

      • 定義一個互斥鎖,用于保護共享數據;
      • 創建一個sync.Cond對象,關聯這個互斥鎖;
      • 在需要等待條件變量的地方,獲取這個互斥鎖,并使用Wait方法等待條件變量被通知;
      • 在需要通知等待的協程時,使用SignalBroadcast方法通知等待的協程。
      • 最后,釋放這個互斥鎖。

      下面是一個簡單的代碼的示例,展示了大概的代碼結構:

      var (
          // 1. 定義一個互斥鎖
          mu    sync.Mutex
          cond  *sync.Cond
          count int
      )
      func init() {
          // 2.將互斥鎖和sync.Cond進行關聯
          cond = sync.NewCond(&mu)
      }
      go func(){
          // 3. 在需要等待的地方,獲取互斥鎖,調用Wait方法等待被通知
          mu.Lock()
          // 這里會不斷循環判斷 是否滿足條件
          for !condition() {
             cond.Wait() // 等待任務
          }
          mu.Unlock()
      }
      
      go func(){
           // 執行業務邏輯
           // 4. 滿足條件,此時調用Broadcast喚醒處于等待狀態的協程
           cond.Broadcast() 
      }
      

      2.4 使用例子

      下面通過描述net/http中的 connReader,來展示使用sync.Cond實現阻塞等待通知的機制。這里我們不需要理解太多,只需要知道connReader下面兩個方法:

      func (cr *connReader) Read(p []byte) (n int, err error) {}
      func (cr *connReader) abortPendingRead() {}
      

      Read方法則是用于從HTTP連接中讀取數據,不允許并發訪問的。而abortPendingRead則是用于終止正在讀取的連接。

      abortPendingRead方法的語意來看,是需要成功終止其他協程進行數據的讀取之后,才能正常返回,也就是此時沒有協程再繼續讀取數據了,才可以返回。

      那abortPendingRead如何得知是否還有協程在讀取數據呢,其實是可以通過定時輪訓connReader的狀態,從而判斷當前Read方法是否仍在讀取數據。但是定時輪訓效率太低,可能會造成cpu的大量空轉。更好的方式,應該是讓協程進入阻塞狀態,然后等條件滿足了,其他協程再來喚醒當前協程,然后再繼續運行下去。

      這個其實就是sync.Cond設計的用途,當不滿足運行條件時,先進入阻塞狀態,等待條件滿足時,再由其他協程來喚醒,然后再繼續運行下去,能夠提高程序的執行效率。其中Wait方法便是讓協程進入阻塞狀態,而SingalBoardcast便是喚醒處于阻塞狀態的協程,告知其條件滿足了,可以繼續向下執行了。

      回到我們connReader的例子,我們使用sync.Cond實現阻塞等待通知的效果。

      type connReader struct {
          // 是否正在讀取數據
          inRead bool
          mu      sync.Mutex // guards following
          cond    *sync.Cond
      }
      
      func (cr *connReader) abortPendingRead() {
          if !cr.inRead{
              return
          }
          //1. 通過一定手段,讓Read方法中斷
          cr.mu.Lock()
          // 判斷Read方法是否仍然在讀取數據
          for cr.inRead {
              //2. 此時Read方法仍然在讀取數據, 不滿足條件,等待通知
              cr.cond.Wait()
          }
          cr.mu.Unlock()
      }
      
      func (cr *connReader) Read(p []byte) (n int, err error) {
           cr.mu.Lock()
           cr.inRead = true
          // 1. 讀取數據
          // 2. abortPendingRead通過某種手段,讓Read方法中斷
          
          cr.inRead = false
          cr.mu.Unlock()
          // 3. 現在已經滿足abortPendingRead繼續執行下去的條件了,可以喚醒abortPendingRead協程了
          cond.Boardcast()
      }
      

      這里abortPendingRead方法首先判斷是否還在讀取數據,是的話,調用Wait方法進入阻塞狀態,等待條件滿足后繼續執行。

      對于Read方法,因為其不運行并發訪問,當其將退出時,說明此時已經沒有協程在讀取數據了,滿足abortPendingRead繼續執行下去的條件了,此時可以調用Boardcast來喚醒等待條件滿足的協程。之后調用abortPendingRead方法的協程此時能夠接收到通知,便能夠順利被喚醒,從而正確返回。

      這里便展示了一個簡單的,使用sync.Cond實現阻塞等待通知的例子。

      3. 原理

      3.1 基本原理

      Sync.Cond存在一個通知隊列,保存了所有處于等待狀態的協程。通知隊列定義如下:

      type notifyList struct {
         wait   uint32
         notify uint32
         lock   uintptr // key field of the mutex
         head   unsafe.Pointer
         tail   unsafe.Pointer
      }
      

      當調用Wait方法時,此時Wait方法會釋放所持有的鎖,然后將自己放到notifyList等待隊列中等待。此時會將當前協程加入到等待隊列的尾部,然后進入阻塞狀態。

      當調用Signal 時,此時會喚醒等待隊列中的第一個協程,其他繼續等待。如果此時沒有處于等待狀態的協程,調用Signal不會有其他作用,直接返回。當調用BoradCast方法時,則會喚醒notfiyList中所有處于等待狀態的協程。

      sync.Cond的代碼實現比較簡單,協程的喚醒和阻塞已經由運行時包實現了,sync.Cond的實現直接調用了運行時包提供的API。

      3.2 實現

      3.2.1 Wait方法實現

      Wait方法首先調用runtime_notifyListAd方法,將自己加入到等待隊列中,然后釋放鎖,等待其他協程的喚醒。

      func (c *Cond) Wait() {
         // 將自己放到等待隊列中
         t := runtime_notifyListAdd(&c.notify)
         // 釋放鎖
         c.L.Unlock()
         // 等待喚醒
         runtime_notifyListWait(&c.notify, t)
         // 重新獲取鎖
         c.L.Lock()
      }
      

      3.2.2 Singal方法實現

      Singal方法調用runtime_notifyListNotifyOne喚醒等待隊列中的一個協程。

      func (c *Cond) Signal() {
         // 喚醒等待隊列中的一個協程
         runtime_notifyListNotifyOne(&c.notify)
      }
      

      3.2.3 Broadcast方法實現

      Broadcast方法調用runtime_notifyListNotifyAll喚醒所有處于等待狀態的協程。

      func (c *Cond) Broadcast() {
         // 喚醒等待隊列中所有的協程
         runtime_notifyListNotifyAll(&c.notify)
      }
      

      4.使用注意事項

      4.1 調用Wait方法前未加鎖

      4.1.1 問題

      如果在調用Wait方法前未加鎖,此時會直接panic,下面是一個簡單例子的說明:

      package main
      
      import (
          "fmt"
          "sync"
          "time"
      )
      
      var (
         count int
         cond  *sync.Cond
         lk    sync.Mutex
      )
      
      func main() {
          cond = sync.NewCond(&lk)
          wg := sync.WaitGroup{}
          wg.Add(2)
          go func() {
             defer wg.Done()
             for {
                time.Sleep(time.Second)
                count++
                cond.Broadcast()
             }
          }()
          
          go func() {
             defer wg.Done()
             for {
                time.Sleep(time.Millisecond * 500)          
                //cond.L.Lock() 
                for count%10 != 0 {
                     cond.Wait()
                }
                t.Logf("count = %d", count)
                //cond.L.Unlock()  
             }
          }()
          wg.Wait()
      }
      

      上面代碼中,協程一每隔1s,將count字段的值自增1,然后喚醒所有處于等待狀態的協程。協程二執行的條件為count的值為10的倍數,此時滿足執行條件,喚醒后將會繼續往下執行。

      但是這里在調用sync.Wait方法前,沒有先獲取鎖,下面是其執行結果,會拋出 fatal error: sync: unlock of unlocked mutex 錯誤,結果如下:

      count = 0
      fatal error: sync: unlock of unlocked mutex
      

      因此,在調用Wait方法前,需要先獲取到與sync.Cond關聯的鎖,否則會直接拋出異常。

      4.1.2 為什么調用Wait方法前需要先獲取該鎖

      強制調用Wait方法前需要先獲取該鎖。這里的原因在于調用Wait方法如果不加鎖,有可能會出現競態條件。

      這里假設多個協程都處于等待狀態,然后一個協程調用了Broadcast喚醒了其中一個或多個協程,此時這些協程都會被喚醒。

      如下,假設調用Wait方法前沒有加鎖的話,那么所有協程都會去調用condition方法去判斷是否滿足條件,然后都通過驗證,執行后續操作。

      for !condition() {
          c.Wait()
      }
      c.L.Lock()
      // 滿足條件情況下,執行的邏輯
      c.L.Unlock()
      

      此時會出現的情況為,本來是需要在滿足condition方法的前提下,才能執行的操作。現在有可能的效果,為前面一部分協程執行時,還是滿足condition條件的;但是后面的協程,盡管不滿足condition條件,還是執行了后續操作,可能導致程序出錯。

      正常的用法應該是,在調用Wait方法前便加鎖,只會有一個協程判斷是否滿足condition條件,然后執行后續操作。這樣子就不會出現即使不滿足條件,也會執行后續操作的情況出現。

      c.L.Lock()
      for !condition() {
          c.Wait()
      }
      // 滿足條件情況下,執行的邏輯
      c.L.Unlock()
      

      4.2 Wait方法接收到通知后,未重新檢查條件變量

      調用sync.Wait方法,協程進入阻塞狀態后被喚醒,沒有重新檢查條件變量,此時有可能仍然處于不滿足條件變量的場景下。然后直接執行后續操作,有可能會導致程序出錯。下面舉一個簡單的例子:

      package main
      
      import (
          "fmt"
          "sync"
          "time"
      )
      
      var (
         count int
         cond  *sync.Cond
         lk    sync.Mutex
      )
      
      func main() {
          cond = sync.NewCond(&lk)
          wg := sync.WaitGroup{}
          wg.Add(3)
          go func() {
             defer wg.Done()
             for {
                time.Sleep(time.Second)
                cond.L.Lock()
                // 將flag 設置為true
                flag = true
                // 喚醒所有處于等待狀態的協程
                cond.Broadcast()
                cond.L.Unlock()
             }
          }()
          
          for i := 0; i < 2; i++ {
             go func(i int) {
                defer wg.Done()
                for {
                   time.Sleep(time.Millisecond * 500)
                   cond.L.Lock()
                   // 不滿足條件,此時進入等待狀態
                   if !flag {
                      cond.Wait()
                   }
                   // 被喚醒后,此時可能仍然不滿足條件
                   fmt.Printf("協程 %d flag = %t", i, flag)
                   flag = false
                   cond.L.Unlock()
                }
             }(i)
          }
          wg.Wait()
      }
      

      在這個例子,我們啟動了一個協程,定時將flag設置為true,相當于每隔一段時間,便滿足執行條件,然后喚醒所有處于等待狀態的協程。

      然后又啟動了兩個協程,在滿足條件的前提下,開始執行后續操作,但是這里協程被喚醒后,沒有重新檢查條件變量,具體看第39行。這里會出現的場景是,第一個協程被喚醒后,此時執行后續操作,然后將flag重新設置為false,此時已經不滿足條件了。之后第二個協程喚醒后,獲取到鎖,沒有重新檢查此時是否滿足執行條件,直接向下執行,這個就和我們預期不符,可能會導致程序出錯,代碼執行效果如下:

      協程 1 flag = true
      協程 0 flag = false
      協程 1 flag = true
      協程 0 flag = false
      

      可以看到,此時協程0執行時,flag的值均為false,說明此時其實并不符合執行條件,可能會導致程序出錯。因此正確用法應該像下面這樣子,被喚醒后,需要重新檢查條件變量,滿足條件之后才能繼續向下執行。

      c.L.Lock()
      // 喚醒后,重新檢查條件變量是否滿足條件
      for !condition() {
          c.Wait()
      }
      // 滿足條件情況下,執行的邏輯
      c.L.Unlock()
      

      5.總結

      本文介紹了 Go 語言中的 sync.Cond 并發原語,它是用于實現 goroutine 之間的同步的重要工具。我們首先學習了 sync.Cond 的基本使用方法,包括創建和使用條件變量、使用WaitSignal/Broadcast方法等。

      在接下來的部分中,我們介紹了 sync.Cond 的實現原理,主要是對等待隊列的使用,從而sync.Cond有更好的理解,能夠更好得使用它。同時,我們也講述了使用sync.Cond的注意事項,如調用Wait方法前需要加鎖等。

      基于以上內容,本文完成了對 sync.Cond 的介紹,希望能夠幫助大家更好地理解和使用Go語言中的并發原語。

      posted @ 2023-03-20 21:49  菜鳥額  閱讀(1299)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲熟女乱一区二区三区| 精品久久精品午夜精品久久| 久久亚洲av成人一二三区| 成人乱人伦精品小说| 国产精品久久久久久久久久妞妞| 各种少妇wbb撒尿| 新婚少妇无套内谢国语播放| 精品亚洲无人区一区二区| 午夜免费无码福利视频麻豆| 东方av四虎在线观看| 国产91丝袜在线观看| 精品国偷自产在线视频99| 曰本丰满熟妇xxxx性| 日韩欧美在线综合网另类| 亚洲精品第一国产综合精品| 青青青爽在线视频观看| 亚洲欧洲色图片网站| 无码人妻斩一区二区三区| 亚洲第一极品精品无码久久| 国产无码高清视频不卡| 白嫩少妇激情无码| 免费观看欧美猛交视频黑人| 免费看亚洲一区二区三区| 亚洲乱妇熟女爽到高潮的片 | 四虎国产精品永久在线下载| 两性午夜刺激性视频| 日韩人妻无码一区二区三区俄罗斯| 国产日韩综合av在线| 国产成人精品无码播放| 玩弄放荡人妻少妇系列| 免费人成视频在线观看网站| 亚洲精品国产一二三区| 亚洲成av人片色午夜乱码| 亚洲国产精品综合久久20| 男人扒开添女人下部免费视频| 熟妇人妻无码中文字幕老熟妇| 精品无码一区二区三区在线| 天堂va欧美ⅴa亚洲va在线| 中文字幕亚洲综合第一页| 精品日本乱一区二区三区| 成人国产精品一区二区不卡|