網站首頁 編程語言 正文
簡介
Go
標準庫提供 Cond
原語的目的是,為等待 / 通知場景下的并發問題提供支持。Cond
通常應用于等待某個條件的一組 goroutine
,等條件變為 true
的時候,其中一個 goroutine
或者所有的 goroutine
都會被喚醒執行。
Cond
是和某個條件相關,這個條件需要一組 goroutine
協作共同完成,在條件還沒有滿足的時候,所有等待這個條件的 goroutine
都會被阻塞住,只有這一組 goroutine
通過協作達到了這個條件,等待的 goroutine 才可能繼續進行下去。
這個條件可以是我們自定義的 true/false
邏輯表達式。
但是 Cond
使用的比較少,因為在大部分場景下是可以被 Channel
和 WaitGroup
來替換的。
詳細介紹
下面就是 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()
NeWCond:
NeWCond
方法需要調用者傳入一個Locker
接口,這個接口就Lock/UnLock
方法,所以我們可以傳入一個sync.Metex
對象Signal:允許調用者喚醒一個等待當前
Cond
的goroutine
。如果Cond
等待隊列中有一個或者多個等待的goroutine
,則從等待隊列中移除第一個goroutine
并把它喚醒Broadcast:允許調用者喚醒所有等待當前
Cond
的goroutine
。如果 Cond 等待隊列中有一個或者多個等待的goroutine
,則清空所有等待的goroutine
,并全部喚醒Wait:會把調用者放入
Cond
的等待隊列中并阻塞,直到被Signal
或者Broadcast
的方法從等待隊列中移除并喚醒
案例:Redis連接池
可以看一下下面的代碼,使用了 Cond
實現一個 Redis
的連接池,最關鍵的代碼就是在鏈表為空的時候需要調用 Cond
的 Wait
方法,將 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)
原文鏈接:https://juejin.cn/post/7093041338836320292
相關推薦
- 2022-05-22 詳解如何創建一個.NET?Core工程_實用技巧
- 2023-11-14 MathType 運行時錯誤‘53’:文件未找到:MathPage.WLL
- 2023-04-24 pandas常用表連接merge/concat/join/append詳解_python
- 2022-05-10 bean作用域 設置創建bean是單實例還是多實例
- 2022-06-16 Python語法學習之正則表達式的使用詳解_python
- 2022-03-29 C#使用Twain協議實現掃描儀連續掃描功能_C#教程
- 2022-03-29 ASP.NET?Core實現動態獲取文件并下載_實用技巧
- 2022-08-03 python基礎之//、/與%的區別詳解_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支