在前面併發性能對比的文章中,咱們能夠看到Golang處理大併發的能力十分強勁,並且開發也特別方便,只須要用go關鍵字便可開啓一個新的協程。數組
但當多個goroutine同時進行處理的時候,就會遇到同時搶佔一個資源的狀況(併發都會遇到的問題),因此咱們但願某個goroutine等待另外一個goroutine處理完某一個步驟以後才能繼續。sync包就是爲了讓goroutine同步而出現的。固然還可使用channel實現,這個後面會介紹到。多線程
鎖有兩種:互斥鎖(mutex)和讀寫鎖(RWMutex)併發
互斥鎖: 當數據被加鎖了以後,除次外的其餘協程不能對數據進行讀操做和寫操做。 這個固然能解決併發程序對資源的操做。可是,效率上是個問題,由於當加鎖後,其餘協程只有等到解鎖後才能對數據進行讀寫操做。性能
讀寫鎖: 讀數據的時候上讀鎖,寫數據的時候上寫鎖。有寫鎖的時候,數據不可讀不可寫。有讀鎖的時候,數據可讀,不可寫。線程
兩種鎖的使用方式相同,這裏就只列出互斥鎖的代碼:協程
package main import ( "sync" "time" "fmt" ) var num = 0 func main () { mu := &sync.Mutex{} for i:=0;i<10000;i++ { go func(){ mu.Lock() defer mu.Unlock() num += 1 }() } time.Sleep(time.Second) fmt.Println("num:", num) // 若是不加鎖這裏的num的值會是一個隨機數而不是10000 }
有的時候,咱們啓動多個相同goroutine,可是裏面的某個操做我只但願被執行一次,這個時候Once就上場了。blog
package main import ( "fmt" "sync" "time" ) func main() { var once sync.Once one := func() { fmt.Println("just once") } for i := 0; i < 10; i++ { go func(a int) { once.Do(one) // 只是被執行一次 }(i) } time.Sleep(time.Millisecond*200) }
當某個操做或是某個goroutine須要等待一批goroutine執行完畢之後才繼續執行,那麼這種多線程(go裏面說的線程就是goroutine)等待的問題就可使用WaitGroup了。資源
代碼以下:開發
package main import ( "sync" "fmt" "time" ) var waitGroup sync.WaitGroup func main () { for i := 0; i < 10; i++ { waitGroup.Add(1) // 添加須要等待goroutine的數量 go func() { fmt.Println("hehe") time.Sleep(time.Second) waitGroup.Done() // 減小須要等待goroutine的數量 至關於Add(-1) } () } waitGroup.Wait() // 執行阻塞,直到全部的須要等待的goroutine數量變成0 fmt.Println("over") }
sync.Cond是用來控制某個條件下,goroutine進入等待時期,等待信號到來,而後從新啓動。get
代碼以下:
package main import ( "fmt" "sync" "time" ) var locker = new(sync.Mutex) var cond = sync.NewCond(locker) func test(x int) { cond.L.Lock() //獲取鎖 cond.Wait()//等待通知 暫時阻塞 fmt.Println(x) time.Sleep(time.Second * 1) cond.L.Unlock()//釋放鎖 } func main() { for i := 0; i < 40; i++ { go test(i) } fmt.Println("start all") time.Sleep(time.Second * 3) fmt.Println("signal1") cond.Signal() // 下發一個通知隨機給已經獲取鎖的goroutine time.Sleep(time.Second * 3) fmt.Println("signal2") cond.Signal()// 下發第二個通知隨機給已經獲取鎖的goroutine time.Sleep(time.Second * 1) // 在廣播以前要等一會,讓全部線程都在wait狀態 fmt.Println("broadcast") cond.Broadcast()//下發廣播給全部等待的goroutine time.Sleep(time.Second * 60) }
上面代碼有幾個要點要特別說明一下:
1. 每一個Cond都必須有個與之關聯的鎖 // 見第9行
2. 協程方法裏面一開始/結束都必須加/解鎖 // 見第12行和16行
3. cond.Wait()時會自動解鎖,當被喚醒時,又會加上鎖。因此第2點提到必須加/解鎖。
channel不只能夠用來goroutine之間的通訊,也可使goroutine同步完成協做。這點主要基於從channel取數據的時候,會阻塞當前goroutine這個特性。示例代碼以下:
package main import ( "fmt" "time" ) var chan1 = make(chan string, 512) var arr1 = []string{"qq","ww","ee","rr","tt"} func chanTest1() { for _, v := range arr1 { chan1 <- v } close(chan1) // 關閉channel } func chanTest2() { for { getStr, ok := <- chan1 // 阻塞,直到chan1裏面有數據 if !ok { // 判斷channel是否關閉或者爲空 return } fmt.Println(getStr) // 按數組順序內容輸出 } } func main () { go chanTest1() go chanTest2() time.Sleep(time.Millisecond*200) }