當我們的程式就乙個執行緒的時候是不需要用到鎖的,但是通常我們實際的**不會是單個執行緒的,所有這個時候就需要用到鎖了,那麼關於鎖的使用場景主要涉及到哪些呢?
互斥鎖:同乙個時刻只有乙個執行緒能夠拿到鎖
我們先通過乙個例子來演示,如果當多個執行緒同時更改乙個變數,結果會是怎麼樣
不加鎖版本
package mainimport (
"sync"
"fmt")
var (
//lock sync.mutex
count
intw sync.waitgroup //用於等待子執行緒執行完之後退出
)func main()
w.done() //執行完 執行w.done
}()for i :=0;i<100000;i++
w.wait() //最後執行w.wait等待所有的執行緒執行完畢
fmt.println(count)
}
當我們執行多次就可以發現,最後的結果基本不可能是我們先看到的:200000
我們修改****需要加鎖保護的地方加上鎖,並且這裡加的是互斥鎖,修改後的**為:
package mainimport (
"sync"
"fmt")
var (
lock sync.mutex
count
intw sync.waitgroup //用於等待子執行緒執行完之後退出
)func main()
w.done() //執行完 執行w.done
}()for i :=0;i<100000;i++
w.wait() //最後執行w.wait等待所有的執行緒執行完畢
fmt.println(count)
}
這次當我們多次執行的時候,就能保證我們每次都能看到我們想要的值:200000
接下來看讀寫鎖
讀寫鎖主要用到讀多寫少的場景
讀寫鎖分為:讀鎖和寫鎖
如果自己設定了乙個寫鎖,那麼其他讀的執行緒以及寫的執行緒都拿不到鎖,這個時候和互斥鎖的功能相同
如果自己設定了乙個讀鎖,那麼其他寫的執行緒是拿不到鎖的,但是其他讀的執行緒都是可以拿到這個鎖
我們把上面的例子**進行更改:
package mainimport (
"sync"
"fmt")
var (
rwlock sync.rwmutex
w sync.waitgroup
count
int)
func main()
w.done()
}()for i:=0;i<1000000;i++
w.wait()
fmt.println(count)
}
通過設定寫鎖,我們同樣可以實現資料的一致性
下面是乙個讀鎖的使用例子:
package mainimport (
"sync"
"fmt")
var (
rwlock sync.rwmutex
w sync.waitgroup
count
int)
func main()
w.done()
}()for i:=0;i<16;i++()
}w.wait()
fmt.println(count)
}
原子操作,我們則不需加鎖,也能保證資料的一致性
並且如果只是計算,那麼原子操作則是最快的
例項**:
package mainimport (
"sync"
//"time"
"sync/atomic"
"fmt")
var (
w sync.waitgroup
count int32
)func main()
w.done()
}()for i:=0;i<1000000;i++
w.wait()
//end :=time.now().unixnano()
start)/1000/1000)
fmt.println(count)
}
互斥鎖需要注意的問題:
1、不要重複鎖定互斥鎖
2、不要忘記解鎖互斥鎖, 必要時使用defer語句
3、不要對尚未鎖定或者已解鎖的互斥鎖解鎖
4、不要對在多個函式之間直接傳遞互斥鎖
對已經鎖定的互斥鎖進行鎖定,會立即阻塞當前的goroutine 這個goroutine所執行的流程會一直停滯在該呼叫互斥鎖的lock方法的那行**
所謂死鎖: 當前程式中的主goroutine以及我們啟用的那些goroutine 都已經被阻塞,這些goroutine可以被稱為使用者級的goroutine 這就相當於整個程式已經停滯不前了,並且這個時候go程式會丟擲如下的panic:
fatal error: all goroutines are asleep - deadlock!並且go語言執行時系統丟擲自行丟擲的panic都屬於致命性錯誤,都是無法被恢復的,呼叫recover函式對他們起不到任何作用
go語言中的互斥鎖是開箱即用的,也就是我們宣告乙個sync.mutex 型別的變數,就可以直接使用它了,需要注意:該型別是乙個結構體型別,屬於值型別的一種,把它傳給乙個函式
將它從函式中返回,把它賦值給其他變數,讓它進入某個通道都會導致他的副本的產生。並且原值和副本以及多個副本之間是完全獨立的,他們都是不同的互斥鎖
所以不應該將鎖通過函式的引數進行傳遞
1、在寫鎖已被鎖定的情況下再次試圖鎖定寫鎖,會阻塞當前的goroutine
2、在寫鎖已被鎖定的情況下再次試圖鎖定讀鎖,也會阻塞當前的goroutine
3、在讀鎖已被鎖定的情況下試圖鎖定寫鎖,同樣會阻塞當前的goroutine
4、在讀鎖已被鎖定的情況下再試圖鎖定讀鎖,並不會阻塞當前的goroutine
對於某個受到讀寫鎖保護的共享資源,多個寫操作不能同時進行,寫操作和讀操作也不能同時進行,但多個讀操作卻可以同時進行
對寫鎖進行解鎖,會喚醒「所有因試圖鎖定讀鎖,而被阻塞的goroutine」, 並且這個通常會使他們都成功完成對讀鎖的鎖定
對讀鎖進行解鎖,只會在沒有其他讀鎖鎖定的前提下,喚醒「因試圖鎖定寫鎖,而被阻塞的goroutine」 並且只會有乙個被喚醒的goroutine能夠成功完成對寫鎖的鎖定,其他的goroutine
還要在原處繼續等待,至於哪乙個goroutine,那麼就要看誰等待的事件最長
解鎖讀寫鎖中未被鎖定的寫鎖, 會立即引發panic ,對其中的讀鎖也是如此,並且同樣是不可恢復的
Go基礎之 介面
在go語言中,乙個類只要實現了介面要求的所有函式,我們就說這個類實現了該介面 inte ce型別可以定義一組方法,用來表示乙個物件的行為特徵,inte ce不能包含任何變數,介面是引用型別。舉個簡單的例子,乙個動物的介面,動物有吃的能力,有叫的能力,等等,這裡省略,假如動物就只有吃和叫的能力。pac...
GO基礎之陣列
一 陣列的宣告與遍歷 package main import fmt 宣告陣列的形式1 var arr 3 int var arr2 4 int func main fmt.println a b int 遍歷陣列方式1 for i 0 i fmt.println 遍歷陣列方式2 for value...
Go 基礎之指標
區別於c c 中的指標,go語言中的指標不能進行偏移和運算,是安全指標。任何程式資料載入記憶體後,在記憶體都有他們的位址,這就是指標。而為了儲存乙個資料在記憶體中的位址,我們就需要指標變數。比如,永遠不要高估自己 這句話是我的座右銘,我想把它寫入程式中,程式一啟動這句話是要載入到記憶體 假設記憶體位...