Golang語言中,處理序間通訊除了使用channel,還可以使用共用記憶體,即sync包。
Q-1
package mainimport ("fmt""time")func main() {for i := 1; i < 6; i++ {go func(x int) {time.Sleep(time.Second)fmt.Println("第", x, "個子goroutine執行結束")}(i)}fmt.Println("主goroutine執行結束")}
上面的代碼毫無疑問的只會輸出:“主goroutine執行結束”,而不會輸出子goroutine執行結束,這是因為,主協程一旦執行完畢,整個進程就會結束。
sync組等待
sync包中常用的有三個方法:Add(num int) 、 Done()、 Wait()
sync中會在內部維持一個類似於計數器的東西,通過Add方法來增加計數器的值,增加參數num;通過Done來減少計數器的值,一次只能減少1;Wait方法會使主協程阻塞,只有到內部計數器的值為0時,阻塞才會解開。
用法如下:
package mainimport ("fmt""sync""time")func main() {var wg sync.WaitGroupfor i := 1; i < 6; i++ {wg.Add(1) //計數器加1go func(x int) {time.Sleep(time.Second)fmt.Println("第", x, "個子goroutine執行結束")wg.Done() //goroutine執行完畢,計數器減1}(i)}wg.Wait() // 計數器不為0的時候,一直阻塞;計數器變為0的時候,阻塞解開fmt.Println("主goroutine執行結束")}
注意:添加的計時器數量必須比減少計時器的數量要少,否則會引發死結。
比如,將計數器值增加了10,但是只執行了9次計數器減少,剩餘一個計數器,計數器的值始終不為0,則主協程一直等待,形成死結。但是較少計數器的次數可以比計數器的值要大,可以提前結束主協程的阻塞。
Q-2
請看下面一段代碼,嘗試在兩個迴圈中,各自使sum加1,執行1000次,乍一看,覺得結果是sum的最終結果是2000,但是真正執行之後,結果不是2000。
package mainimport ("fmt""sync")func main() {var wg sync.WaitGroupwg.Add(2)var sum int = 0go func() {for i := 0; i < 1000; i++ {sum++}wg.Done()}()go func() {for i := 0; i < 1000; i++ {sum++}wg.Done()}()wg.Wait()fmt.Println(sum)}
sync互斥鎖
sync互斥鎖有兩個常用的方法,Lock()加鎖,Unlock()解鎖。使用Lock加鎖後,不能再進行加鎖,只有當對其進行Unlock解鎖之後,才能對其加鎖。這個很好理解。
如果對一個未加鎖的資源進行解鎖,會引發panic異常。
可以在一個goroutine中對一個資源加鎖,而在另外一個goroutine中對該資源進行解鎖。
使用樣本如下:
package mainimport ("fmt""sync")func main() {var wg sync.WaitGroupvar mutex sync.Mutexwg.Add(2)var sum int = 0go func() {for i := 0; i < 1000; i++ {mutex.Lock() //上鎖sum++mutex.Unlock()//解鎖}wg.Done()}()go func() {for i := 0; i < 1000; i++ {mutex.Lock() //加鎖sum++mutex.Unlock() //解鎖}wg.Done()}()wg.Wait()fmt.Println(sum)}
sync讀寫互斥鎖(多讀單寫鎖)
允許對同一資源進行同時讀,但是不能進行同時寫,也不能進行一邊讀,一邊寫。
如果一個資源被加了寫鎖,那麼就不能再給他加其他鎖,包括寫鎖,只能等寫鎖解開後,才能繼續加其他鎖。如果一個資源被加了讀鎖,那麼,仍然可以給該資源加讀鎖,但是不能加寫鎖,必須等讀鎖釋放後,才能加寫鎖。
sync.RWMutex有四個常用的方法:
Lock()加寫鎖 、 Unlock()取消寫鎖 、 RLock()加讀鎖 、 RUnlock()釋放讀鎖
使用樣本:
package mainimport ("fmt""sync""time")var rwMutex sync.RWMutex //定義一個讀寫互斥鎖//注意傳遞的組等待是指標形式func WriteData(wg *sync.WaitGroup, id int) {rwMutex.Lock() //加 寫鎖fmt.Println("增加第", id, "個寫鎖")for i := 0; i < 5; i++ {fmt.Println("進行寫操作")time.Sleep(time.Second)}fmt.Println("釋放第", id, "個寫鎖")rwMutex.Unlock() //釋放 寫鎖wg.Done()}func ReadData(wg *sync.WaitGroup, id int) {rwMutex.RLock() //加 讀鎖fmt.Println("增加第", id, "個讀鎖")for i := 0; i < 5; i++ {fmt.Println("進行讀操作")time.Sleep(time.Second)}fmt.Println("釋放第", id, "個讀鎖")rwMutex.RUnlock() //釋放 讀鎖wg.Done()}func main() {var wg sync.WaitGroupfor i := 1; i < 3; i++ {wg.Add(1)go WriteData(&wg, i)}for i := 1; i < 4; i++ {wg.Add(1)go ReadData(&wg, i)}wg.Wait()fmt.Println("主程式執行完畢")}
運行之後,就會發現,只有一個寫加鎖,只有寫解鎖之後,才允許加其他鎖;如果是加讀鎖之後,可以再加讀鎖。
Q-3
package mainimport ("fmt""sync")func demo() {fmt.Println("運行demo函數")}func main() {var wg sync.WaitGroupfor i := 0; i < 10; i++ {wg.Add(1)go func() {demo()wg.Done()}()}wg.Wait()fmt.Println("主程式結束")}
這個範例程式碼運行之後,會輸出10次“運行demo函數”,因為有10個goroutine,每個goroutine都運行了1次。
現在的需求是:雖然有10個goroutine,但是希望只有一個goroutine中執行demo函數,如果一個goroutine執行過了demo函數,其他的goroutine就會忽略demo函數,不去執行demo,保證demo函數只被執行一次。
sync.once初始化
sync.Once就是用來解決上面這個問題的:Once的作用就是多次調用,但只執行一次,Once只有一個方法->Once.Do(func),向Do中傳入一個函數,在第一次執行Once.Do()的時候,才執行傳入的func,以後在執行Once.Do(),就不會再執行傳入的func。
樣本如下:
package mainimport ("fmt""sync")func demo() {fmt.Println("運行demo函數")}func main() {var wg sync.WaitGroupvar once sync.Oncefor i := 0; i < 10; i++ {wg.Add(1)go func() {once.Do(demo)//注意不要寫成once.Do(demo()),這樣寫是把demo()的運行結果傳入Dowg.Done()}()}wg.Wait()fmt.Println("主程式結束")}
這樣就不會執行多次demo(),而只輸出一次“運行demo函數”