select
-
select 的作用:
通過 select 可以監(jiān)聽 channel 上的數(shù)據(jù)流動。
-
select 的用法:
與 switch case 語法類似。但是 case 后面必須是 IO 操作,不可以人一些判斷表達(dá)式。
select { case <-chan1: // 如果 chan1 成功讀到數(shù)據(jù),則進(jìn)行該 case 處理語句 case chan2 <- 1: //如果成功向 chan2 寫入數(shù)據(jù),則進(jìn)行該 case 處理語句 default: // 如果上面都沒有成功,則進(jìn)入 default 處理流程 } -
注意:
- 監(jiān)聽的 case 中,沒有滿足監(jiān)聽條件,阻塞。
- 監(jiān)聽的 case 中,有多個滿足監(jiān)聽條件,任選一個執(zhí)行。
- 可以使用 default 來處理所有case 都不滿足監(jiān)聽條件的狀況。通常不用(會產(chǎn)生忙輪詢)。
- select 自身不帶有循環(huán)機制,需借助外層 for 來循環(huán)監(jiān)聽。
- break 跳出 select 中的 case 選項。類似于 switch 中的用法。
超時處理
有時候會出現(xiàn) goroutine 阻塞的情況,可以使用 select 來設(shè)置超時,通過如下的方式實現(xiàn):
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <-c:
fmt.Println(v)
case <time.After(5 * time.Second):
fmt.Println("timeout"):
o <- true
break
}
}
}()
// c <-66 // 注釋掉,引發(fā) timeout
<-o
}
鎖和條件變量
什么是鎖
就是某個協(xié)程(線程)在訪問某個資源時先鎖住,防止其他協(xié)程的訪問,等訪問完畢解鎖后其他協(xié)程再來加鎖進(jìn)行訪問。
死鎖
死鎖不是鎖的一種,是一種錯誤使用鎖導(dǎo)致的現(xiàn)象。
死鎖是指兩個或兩個以上的進(jìn)程在進(jìn)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象,若無外力作用,他們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖。
- 常見的場景:
- 單 go 程自己死鎖
package main
import "fmt"
func main() {
ch := make(chan int)
ch <- 1
fmt.Println("send")
go func() {
<- ch
fmt.Println("received")
}()
fmt.Println("over")
}
互斥鎖
每個資源都對應(yīng)一個可稱為 “互斥鎖” 的標(biāo)記,這個標(biāo)記用來保證在任意時刻,只能有一個協(xié)程(線程)訪問該資源。其它的協(xié)程只能等待。
互斥鎖是傳統(tǒng)并發(fā)編程對共享資源進(jìn)行訪問控制的主要手段。它由標(biāo)準(zhǔn)庫 sync 中的 Mutex 結(jié)構(gòu)體類型表示。 sync.Mutex 類型只有兩個公開的指針方法,Lock 和 Unlock。Lock 鎖定當(dāng)前的共享資源,Unlock 進(jìn)行解鎖。
在使用互斥鎖時,一定要注意:對資源操作完成后,一定要解鎖,否則會出現(xiàn)流程執(zhí)行異常、死鎖等問題。通常借助 defer。鎖定后,立即使用 defer 語句保證互斥鎖及時解鎖。如下所示:
var mutex sync.Mutex
func write() {
mutex.Lock()
defer mutex.Unlock()
}
讀寫鎖
互斥鎖的本質(zhì)是當(dāng)一個 goroutine 訪問的時候,其他 goroutine 都不能訪問。這樣在資源同步,避免競爭的同時也降低了程序的并發(fā)性能。程序由原來的并行執(zhí)行變成了串行執(zhí)行。
讀寫鎖可以讓多個讀操作并發(fā),同時讀取,但是對于寫操作是完全互斥的。也就是說,當(dāng)一個 goroutine 進(jìn)行寫操作的時候,其他 goroutine 既不能進(jìn)行讀操作,也不能進(jìn)行寫操作。
讀時共享,寫時獨占。寫鎖優(yōu)先級比讀鎖高。
-
“寫鎖定”和“讀鎖定”
func (*RWMutex) Lock() func (*RWMutex) Unlock() -
“讀鎖定”和“寫鎖定”
func (*RWMutex) RLock() func (*RWMutex) RUnlock()
條件變量
條件變量的作用并不能保證在同一時刻僅有一個協(xié)程(線程)訪問某個共享的數(shù)據(jù)資源,而是在對應(yīng)的共享數(shù)據(jù)的狀態(tài)發(fā)生變化時,通知阻塞在某個條件上的協(xié)程(線程)。條件變量不是鎖,在并發(fā)中不能達(dá)到同步的目的,因此條件變量總是與鎖一塊使用。
Go 標(biāo)準(zhǔn)庫中的 sys.Cond 類型代表了條件變量。條件變量要與鎖(互斥鎖或者讀寫鎖)一起使用。成員變量 L 代表與條件變量搭配使用的鎖。
type Cond struct {
noCopy noCopy
L Locker
notify notifyList
checker copyChecker
}
- 對應(yīng)的 3 個常用方法,Wait、Signal、Broadcast:
-
func (c *Cond) Wait()
該函數(shù)的作用:- 阻塞等待條件變量滿足
- 釋放已掌握的互斥鎖,相當(dāng)于
cond.Unlock()。注意:兩步為原子操作(不會被線程調(diào)度機制打斷的操作;這種操作一旦開始,就一直運行到結(jié)束)。 - 當(dāng)被喚醒,
Wait()函數(shù)返回時,解除阻塞并重新獲取互斥鎖。相當(dāng)于cond.Lock()
-
func (c *Cond) Signal()
單發(fā)通知,給一個正等待(阻塞)在該條件變量上的 goroutine(線程)發(fā)送通知。 -
func (c *Cond) Broadcast()
廣播通知,給正在等待(阻塞)在該條件變量上的所有 goroutine(線程)發(fā)送通知。
-
- 使用流程:
- 創(chuàng)建條件變量:
var cond sync.Cond - 指定條件變量用的鎖:
cond.L = new(sync.Mutex) -
cond.Lock()給公共區(qū)加鎖(互斥量) - 判斷是否達(dá)到阻塞條件(緩沖區(qū)滿/空) --- for 循環(huán)判斷
- 訪問公共區(qū) ---讀、寫數(shù)據(jù)、打印
- 解鎖條件變量用的鎖
cond.L.Unlock() - 喚醒阻塞在條件變量上的對端。
signal() Broadcast()
- 創(chuàng)建條件變量: