一、前言
go語言類似Java JUC包也提供了一些列用於多線程之間進行同步的措施,比如低級的同步措施有 鎖、CAS、原子變量操作類。相比Java來說go提供了獨特的基於通道的同步措施。本節我們先來看看go中與鎖相關的條件變量
二、條件變量
在java中條件變量是與具體的鎖想關聯的,在go中也是這樣的。
package main
import (
"fmt"
"sync"
"time"
)
var (
counter int //計數器
wg sync.WaitGroup //信號量
lock sync.Mutex //互斥鎖
cond = sync.NewCond(&lock) //條件變量
)
func main() {
//1.獲取鎖
cond.L.Lock()
fmt.Println("main thread got lock ")
//2.開啓線程執行一些事情
go doSomething()
//3.用與鎖關聯的條件變量的wait方法
fmt.Println("main thread begin wait ")
cond.Wait()
fmt.Println("main thread end wait ")
//4.釋放鎖
cond.L.Unlock()
fmt.Println("main thread release lock ")
}
func doSomething() {
//2.1激活阻塞到條件變量的wait方法的一個線程
time.Sleep(time.Second * 2)
//2.2獲取鎖
fmt.Println("sub thread begin get lock ")
//cond.L.Lock()
fmt.Println("sub thread got lock ")
time.Sleep(5 * time.Second)
cond.Signal()
//2.3釋放鎖
//cond.L.Unlock()
fmt.Println("sub thread release lock ")
}
- go中使用sync.NewCond(&lock)創建一個條件變量,其中lock可以是互斥鎖或者讀寫鎖
- 主線程線程先獲取了lock鎖(cond.L就是lock變量),然後開啓了子線程,然後調用了條件變量的wait方法,main線程然後被阻塞,並且main線程會釋放獲取的lock鎖。
- 子線程則首先嚐試獲取lock鎖,如果是在main線程執行條件變量的wait前嘗試獲取鎖,則子線程會被阻塞,等main線程執行到wait後,main函數釋放鎖後,子線程會獲取鎖成功。
- 子線程獲取鎖成功後,會先休眠5s然後釋放鎖,然後調用條件變量的signal方法,這時候main線程則會重新獲取到鎖,然後從wait返回。
需要注意的是調用條件變量的signal方法的線程在調用該方法前,獲取關聯的lock鎖這個並不是必須的,讀者可以註釋獲取和釋放鎖代碼,也是OK的。
與Java中類似調用條件變量的signal會激活一個線程,調用Broadcast會激活所有阻塞到條件變量wait方法的線程。
另外需要注意,一般調用線程應該使用循環檢查方式調用條件變量的wait方法,以避免虛假喚醒等問題。
三、總結
go中條件變量與Java中條件變量類似,但是也有不同,相同在於條件變量都是與鎖關聯的,並且只有當線程獲取到鎖後纔可以調用其關聯的條件變量的wait方法,否則會拋出異常,另外當線程阻塞到wait方法後,當前線程會釋放已經獲取的鎖。不同在於Java中只有當線程獲取到鎖後纔可以調用其關聯的條件變量的signal方法,否則會拋出異常,但是在go中調用線程調用signal前獲取鎖不是必須的。