先回顧一下,在 C 或者其它編程語言的併發編程中,主要存在兩種通訊(IPC):git
- 進程間通訊:管道、消息隊列、信號等
- 線程間通訊:互斥鎖、條件變量等
利用以上通訊手段採起的同步措施,最終是爲了達到如下兩種目的:github
- 維持共享數據一致性,併發安全
- 控制流程管理,更好的協同工做
Go語言中除了保留了傳統的同步支持,還提供了特有的 CSP 併發編程模型。編程
接下來經過一個「作累加」的示例程序,展現競爭狀態(race condition)。安全
開啓 5000 個 goroutine,讓每一個 goroutine 給 counter 加 1,最終在全部 goroutine 都完成任務時 counter 的值應該爲 5000,先試下不加鎖的示例程序表現如何併發
func TestDemo1(t *testing.T) { counter := 0 for i := 0; i < 5000; i++ { go func() { counter++ }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) }
結果編程語言
=== RUN TestDemo1 a1_test.go:18: counter = 4663 --- PASS: TestDemo1 (1.00s) PASS
多試幾回,結果一直是小於 5000 的不定值。
競爭狀態下程序行爲的圖像表示性能
將剛剛的代碼稍做改動測試
func TestDemo2(t *testing.T) { var mut sync.Mutex // 聲明鎖 counter := 0 for i := 0; i < 5000; i++ { go func() { mut.Lock() // 加鎖 counter++ mut.Unlock() // 解鎖 }() } time.Sleep(1 * time.Second) t.Logf("counter = %d", counter) }
結果spa
=== RUN TestDemo2 a1_test.go:35: counter = 5000 --- PASS: TestDemo2 (1.01s) PASS
counter = 5000,返回的結果對了。線程
這就是互斥鎖,在代碼上建立一個臨界區(critical section),保證串行操做(同一時間只有一個 goroutine 執行臨界區代碼)。
那麼互斥鎖是怎麼串行的呢?把每一步的執行過程打印出來看下
func TestDemo3(t *testing.T) { var mut sync.Mutex counter := 0 go func() { mut.Lock() log.Println("goroutine B Lock") counter = 1 log.Println("goroutine B counter =", counter) time.Sleep(5 * time.Second) mut.Unlock() log.Println("goroutine B Unlock") }() time.Sleep(1 * time.Second) mut.Lock() log.Println("goroutine A Lock") counter = 2 log.Println("goroutine A counter =", counter) mut.Unlock() log.Println("goroutine A Unlock") }
結果
=== RUN TestDemo3 2020/09/30 22:14:00 goroutine B Lock 2020/09/30 22:14:00 goroutine B counter = 1 2020/09/30 22:14:05 goroutine B Unlock 2020/09/30 22:14:05 goroutine A Lock 2020/09/30 22:14:05 goroutine A counter = 2 2020/09/30 22:14:05 goroutine A Unlock --- PASS: TestDemo3 (5.00s) PASS
經過每一個操做記錄下來的時間能夠看出,goroutine A 的 Lock 一直阻塞到了 goroutine B 的 Unlock。
這時候有個疑問,那 goroutine B 上的鎖,goroutine A 能解鎖嗎?修改一下剛纔的代碼,試一下
func TestDemo5(t *testing.T) { var mut sync.Mutex counter := 0 go func() { mut.Lock() log.Println("goroutine B Lock") counter = 1 log.Println("goroutine B counter =", counter) time.Sleep(5 * time.Second) //mut.Unlock() //log.Println("goroutine B Unlock") }() time.Sleep(1 * time.Second) mut.Unlock() log.Println("goroutine A Unlock") counter = 2 log.Println("goroutine A counter =", counter) time.Sleep(2 * time.Second) }
結果
=== RUN TestDemo5 2020/09/30 22:15:03 goroutine B Lock 2020/09/30 22:15:03 goroutine B counter = 1 2020/09/30 22:15:04 goroutine A Unlock 2020/09/30 22:15:04 goroutine A counter = 2 --- PASS: TestDemo5 (3.01s) PASS
測試經過,未報錯,counter 的值也被成功修改,證實B上的鎖,是能夠被A解開的。
再進一步,goroutine A 不解鎖,直接修改已經被 goroutine B 鎖住的 counter 的值能夠嗎?試一下
func TestDemo6(t *testing.T) { var mut sync.Mutex counter := 0 go func() { mut.Lock() log.Println("goroutine B Lock") counter = 1 log.Println("goroutine B counter =", counter) time.Sleep(5 * time.Second) mut.Unlock() log.Println("goroutine B Unlock") }() time.Sleep(1 * time.Second) //log.Println("goroutine A Unlock") //mut.Unlock() counter = 2 log.Println("goroutine A counter =", counter) time.Sleep(10 * time.Second) }
結果
=== RUN TestDemo6 2020/09/30 22:15:43 goroutine B Lock 2020/09/30 22:15:43 goroutine B counter = 1 2020/09/30 22:15:44 goroutine A counter = 2 2020/09/30 22:15:48 goroutine B Unlock --- PASS: TestDemo6 (11.00s) PASS
測試經過,未報錯,證實B上的鎖,A能夠不用解鎖直接改。
當互斥鎖不斷地試圖得到一個永遠沒法得到的鎖時,它可能會遇到飢餓問題。
在版本1.9中,Go經過添加一個新的飢餓模式來解決先前的問題,全部等待鎖定超過一毫秒的 goroutine,也稱爲有界等待,將被標記爲飢餓。當標記爲飢餓時,解鎖方法如今將把鎖直接移交給第一位等待着。
讀寫鎖和上面的多也差很少,有這麼幾種狀況
不管是互斥鎖仍是讀寫鎖在程序運行時必定是成對的,否則就會引起不可恢復的panic。