中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Go并發編程sync.Cond怎么使用

發布時間:2022-05-05 10:06:41 來源:億速云 閱讀:159 作者:iii 欄目:開發技術

本篇內容主要講解“Go并發編程sync.Cond怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Go并發編程sync.Cond怎么使用”吧!

簡介

Go 標準庫提供 Cond 原語的目的是,為等待 / 通知場景下的并發問題提供支持。Cond 通常應用于等待某個條件的一組 goroutine,等條件變為 true 的時候,其中一個 goroutine 或者所有的 goroutine 都會被喚醒執行。

Cond 是和某個條件相關,這個條件需要一組 goroutine 協作共同完成,在條件還沒有滿足的時候,所有等待這個條件的 goroutine 都會被阻塞住,只有這一組 goroutine 通過協作達到了這個條件,等待的 goroutine 才可能繼續進行下去。

這個條件可以是我們自定義的 true/false 邏輯表達式。

但是 Cond 使用的比較少,因為在大部分場景下是可以被 ChannelWaitGroup 來替換的。

詳細介紹

下面就是 Cond 的數據結構和對外提供的方法,Cond 內部維護了一個等待隊列和鎖實例。

type Cond struct {
   noCopy noCopy

   // 鎖
   L Locker

   // 等待隊列
   notify  notifyList
   checker copyChecker
}

func NeWCond(l Locker) *Cond
func (c *Cond) Broadcast()
func (c *Cond) Signal()
func (c *Cond) Wait()
  • NeWCondNeWCond 方法需要調用者傳入一個 Locker 接口,這個接口就 Lock/UnLock 方法,所以我們可以傳入一個 sync.Metex 對象

  • Signal:允許調用者喚醒一個等待當前 Condgoroutine。如果 Cond 等待隊列中有一個或者多個等待的 goroutine ,則從等待隊列中移除第一個 goroutine 并把它喚醒

  • Broadcast:允許調用者喚醒所有等待當前 Condgoroutine。如果 Cond 等待隊列中有一個或者多個等待的 goroutine,則清空所有等待的 goroutine,并全部喚醒

  • Wait:會把調用者放入 Cond 的等待隊列中并阻塞,直到被 Signal 或者 Broadcast 的方法從等待隊列中移除并喚醒

案例:Redis連接池

可以看一下下面的代碼,使用了 Cond 實現一個 Redis 的連接池,最關鍵的代碼就是在鏈表為空的時候需要調用 CondWait 方法,將 gorutine 進行阻塞。然后 goruntine 在使用完連接后,將連接返回池子后,需要通知其他阻塞的 goruntine 來獲取連接。

package main

import (
   "container/list"
   "fmt"
   "math/rand"
   "sync"
   "time"
)

// 連接池
type Pool struct {
   lock    sync.Mutex // 鎖
   clients list.List  // 連接
   cond    *sync.Cond // cond實例
   close   bool       // 是否關閉
}

// Redis Client
type Client struct {
   id int32
}

// 創建Redis Client
func NewClient() *Client {
   return &Client{
      id: rand.Int31n(100000),
   }
}

// 關閉Redis Client
func (this *Client) Close() {
   fmt.Printf("Client:%d 正在關閉", this.id)
}

// 創建連接池
func NewPool(maxConnNum int) *Pool {
   pool := new(Pool)
   pool.cond = sync.NewCond(&pool.lock)

   // 創建連接
   for i := 0; i < maxConnNum; i++ {
      client := NewClient()
      pool.clients.PushBack(client)
   }

   return pool
}

// 從池子中獲取連接
func (this *Pool) Pull() *Client {
   this.lock.Lock()
   defer this.lock.Unlock()

   // 已關閉
   if this.close {
      fmt.Println("Pool is closed")
      return nil
   }

   // 如果連接池沒有連接 需要阻塞
   for this.clients.Len() <= 0 {
      this.cond.Wait()
   }

   // 從鏈表中取出頭節點,刪除并返回
   ele := this.clients.Remove(this.clients.Front())
   return ele.(*Client)
}

// 將連接放回池子
func (this *Pool) Push(client *Client) {
   this.lock.Lock()
   defer this.lock.Unlock()

   if this.close {
      fmt.Println("Pool is closed")
      return
   }

   // 向鏈表尾部插入一個連接
   this.clients.PushBack(client)

   // 喚醒一個正在等待的goruntine
   this.cond.Signal()
}

// 關閉池子
func (this *Pool) Close() {
   this.lock.Lock()
   defer this.lock.Unlock()

   // 關閉連接
   for e := this.clients.Front(); e != nil; e = e.Next() {
      client := e.Value.(*Client)
      client.Close()
   }

   // 重置數據
   this.close = true
   this.clients.Init()
}

func main() {

   var wg sync.WaitGroup

   pool := NewPool(3)
   for i := 1; i <= 10; i++ {
      wg.Add(1)
      go func(index int) {

         defer wg.Done()

         // 獲取一個連接
         client := pool.Pull()

         fmt.Printf("Time:%s | 【goruntine#%d】獲取到client[%d]\n", time.Now().Format("15:04:05"), index, client.id)
         time.Sleep(time.Second * 5)
         fmt.Printf("Time:%s | 【goruntine#%d】使用完畢,將client[%d]放回池子\n", time.Now().Format("15:04:05"), index, client.id)

         // 將連接放回池子
         pool.Push(client)
      }(i)
   }

   wg.Wait()
}

運行結果:

Time:15:10:25 | 【goruntine#7】獲取到client[31847]
Time:15:10:25 | 【goruntine#5】獲取到client[27887]
Time:15:10:25 | 【goruntine#10】獲取到client[98081]
Time:15:10:30 | 【goruntine#5】使用完畢,將client[27887]放回池子
Time:15:10:30 | 【goruntine#6】獲取到client[27887]               
Time:15:10:30 | 【goruntine#10】使用完畢,將client[98081]放回池子
Time:15:10:30 | 【goruntine#7】使用完畢,將client[31847]放回池子 
Time:15:10:30 | 【goruntine#1】獲取到client[31847]               
Time:15:10:30 | 【goruntine#9】獲取到client[98081]               
Time:15:10:35 | 【goruntine#6】使用完畢,將client[27887]放回池子
Time:15:10:35 | 【goruntine#3】獲取到client[27887]              
Time:15:10:35 | 【goruntine#1】使用完畢,將client[31847]放回池子
Time:15:10:35 | 【goruntine#4】獲取到client[31847]              
Time:15:10:35 | 【goruntine#9】使用完畢,將client[98081]放回池子
Time:15:10:35 | 【goruntine#2】獲取到client[98081]              
Time:15:10:40 | 【goruntine#3】使用完畢,將client[27887]放回池子
Time:15:10:40 | 【goruntine#8】獲取到client[27887]              
Time:15:10:40 | 【goruntine#2】使用完畢,將client[98081]放回池子
Time:15:10:40 | 【goruntine#4】使用完畢,將client[31847]放回池子
Time:15:10:45 | 【goruntine#8】使用完畢,將client[27887]放回池子

注意點

  • 在調用 Wait 方法前,需要先加鎖,就像我上面例子中 Pull 方法也是先加鎖

看一下源碼就知道了,因為 Wait 方法的執行邏輯是先將 goruntine 添加到等待隊列中,然后釋放鎖,然后阻塞,等喚醒后,會繼續加鎖。如果在調用 Wait 前不加鎖,但是里面會解鎖,執行的時候就會報錯。

//
//    c.L.Lock()
//    for !condition() {
//        c.Wait()
//    }
//    ... make use of condition ...
//    c.L.Unlock()
//
func (c *Cond) Wait() {
   c.checker.check()
   
   // 添加到等待隊列
   t := runtime_notifyListAdd(&c.notify)
   c.L.Unlock()
   
   // 阻塞
   runtime_notifyListWait(&c.notify, t)
   c.L.Lock()
}
  • 還是 Wait 方法,在喚醒后需要繼續檢查 Cond 條件

就拿上面的 redis 連接案例來進行說明吧,我這里是使用了 for 循環來進行檢測。如果將 for 循環改成使用 if,也就是只判斷一次,會有什么問題?可以停下來先想想

上面說了調用者也可以使用 Broadcast 方法來喚醒 goruntine ,如果使用的是 Broadcast 方法,所有的 goruntine 都會被喚醒,然后大家都去鏈表中去獲取 redis 連接了,就會出現部分 goruntine拿不到連接,實際上沒有那么多連接可以獲取,因為每次只會放回一個連接到池子中。

// 如果連接池沒有連接 需要阻塞
for this.clients.Len() <= 0 {
  this.cond.Wait()
}

// 獲取連接
ele := this.clients.Remove(this.clients.Front())
return ele.(*Client)

到此,相信大家對“Go并發編程sync.Cond怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

龙口市| 日喀则市| 湘阴县| 灵台县| 三穗县| 忻城县| 清水河县| 当涂县| 孟连| 霍林郭勒市| 扶绥县| 贡嘎县| 江山市| 保靖县| 得荣县| 桓仁| 曲阳县| 望江县| 嘉祥县| 石门县| 衡南县| 敦化市| 鲜城| 札达县| 南召县| 呼和浩特市| 邵阳市| 兴安盟| 峨眉山市| 商丘市| 斗六市| 河曲县| 哈密市| 南昌县| 博客| 咸丰县| 湟源县| 南川市| 车险| 石阡县| 漠河县|