golang中實現併發很是簡單,只需在須要併發的函數前面添加關鍵字"go",可是如何處理go併發機制中不一樣goroutine之間的同步與通訊,golang 中提供了sync包和channel機制來解決這一問題.java
-
type Cond
-
func NewCond(l Locker) *Cond
-
func (c *Cond) Broadcast()
-
func (c *Cond) Signal()
-
func (c *Cond) Wait()
-
type Locker
-
type Mutex
-
func (m *Mutex) Lock()
-
func (m *Mutex) Unlock()
-
type Once
-
func (o *Once) Do(f func())
-
type Pool
-
func (p *Pool) Get() interface{}
-
func (p *Pool) Put(x interface{})
-
type RWMutex
-
func (rw *RWMutex) Lock()
-
func (rw *RWMutex) RLock()
-
func (rw *RWMutex) RLocker() Locker
-
func (rw *RWMutex) RUnlock()
-
func (rw *RWMutex) Unlock()
-
type WaitGroup
-
func (wg *WaitGroup) Add(delta int)
-
func (wg *WaitGroup) Done()
-
func (wg *WaitGroup) Wait()
而golang中的同步是經過sync.WaitGroup來實現的.WaitGroup的功能:它實現了一個相似隊列的結構,能夠一直向隊列中添加任務,當任務完成後便從隊列中刪除,若是隊列中的任務沒有徹底完成,能夠經過Wait()函數來出發阻塞,防止程序繼續進行,直到全部的隊列任務都完成爲止.緩存
WaitGroup總共有三個方法:Add(delta int), Done(), Wait()。
Add:添加或者減小等待goroutine的數量
Done:至關於Add(-1)
Wait:執行阻塞,直到全部的WaitGroup數量變成0併發
具體例子以下:函數
package main
import (
"fmt"
"sync"
)
var waitgroup sync.WaitGroup
func Afunction(shownum int) {
fmt.Println(shownum)
waitgroup.Done() //任務完成,將任務隊列中的任務數量-1,其實.Done就是.Add(-1)
}
func main() {
for i := 0; i < 10; i++ {
waitgroup.Add(1) //每建立一個goroutine,就把任務隊列中任務的數量+1
go Afunction(i)
}
waitgroup.Wait() //.Wait()這裏會發生阻塞,直到隊列中全部的任務結束就會解除阻塞
}
使用場景:
程序中須要併發,須要建立多個goroutine,而且必定要等這些併發所有完成後才繼續接下來的程序執行.WaitGroup的特色是Wait()能夠用來阻塞直到隊列中的全部任務都完成時才解除阻塞,而不須要sleep一個固定的時間來等待.可是其缺點是沒法指定固定的goroutine數目.code
Channel機制:協程
相對sync.WaitGroup而言,golang中利用channel實習同步則簡單的多.channel自身能夠實現阻塞,其經過<-進行數據傳遞,channel是golang中一種內置基本類型,對於channel操做只有4種方式:隊列
建立channel(經過make()函數實現,包括無緩存channel和有緩存channel);進程
向channel中添加數據(channel<-data);同步
從channel中讀取數據(data<-channel);
關閉channel(經過close()函數實現,關閉以後沒法再向channel中存數據,可是能夠繼續從channel中讀取數據)
channel分爲有緩衝channel和無緩衝channel,兩種channel的建立方法以下:
var ch = make(chan int) //無緩衝channel,等同於make(chan int ,0)
var ch = make(chan int,10) //有緩衝channel,緩衝大小是5
其中無緩衝channel在讀和寫是都會阻塞,而有緩衝channel在向channel中存入數據沒有達到channel緩存總數時,能夠一直向裏面存,直到緩存已滿才阻塞.因爲阻塞的存在,因此使用channel時特別注意使用方法,防止死鎖的產生.例子以下:
無緩存channel:
package main
import "fmt"
func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
}
func main() {
ch := make(chan int) //無緩衝的channel
go Afuntion(ch)
ch <- 1
// 輸出結果:
// finish
}
代碼分析:首先建立一個無緩衝channel ch, 而後執行 go Afuntion(ch),此時執行<-ch,則Afuntion這個函數便會阻塞,再也不繼續往下執行,直到主進程中ch<-1向channel ch 中注入數據才解除Afuntion該協程的阻塞.
package main
import "fmt"
func Afuntion(ch chan int) {
fmt.Println("finish")
<-ch
}
func main() {
ch := make(chan int) //無緩衝的channel
//只是把這兩行的代碼順序對調一下
ch <- 1
go Afuntion(ch)
// 輸出結果:
// 死鎖,無結果
}
代碼分析:首先建立一個無緩衝的channel, 而後在主協程裏面向channel ch 中經過ch<-1命令寫入數據,則此時主協程阻塞,就沒法執行下面的go Afuntions(ch),天然也就沒法解除主協程的阻塞狀態,則系統死鎖
總結:
對於無緩存的channel,放入channel和從channel中向外面取數據這兩個操做不能放在同一個協程中,防止死鎖的發生;同時應該先利用go 開一個協程對channel進行操做,此時阻塞該go 協程,而後再在主協程中進行channel的相反操做(與go 協程對channel進行相反的操做),實現go 協程解鎖.即必須go協程在前,解鎖協程在後.
帶緩存channel:
對於帶緩存channel,只要channel中緩存不滿,則能夠一直向 channel中存入數據,直到緩存已滿;同理只要channel中緩存不爲0,即可以一直從channel中向外取數據,直到channel緩存變爲0纔會阻塞.
因而可知,相對於不帶緩存channel,帶緩存channel不易形成死鎖,能夠同時在一個goroutine中放心使用,
close():
close主要用來關閉channel通道其用法爲close(channel),而且實在生產者的地方關閉channel,而不是在消費者的地方關閉.而且關閉channel後,便不可再想channel中繼續存入數據,可是能夠繼續從channel中讀取數據.例子以下:
package main
import "fmt"
func main() {
var ch = make(chan int, 20)
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
//ch <- 11 //panic: runtime error: send on closed channel
for i := range ch {
fmt.Println(i) //輸出0 1 2 3 4 5 6 7 8 9
}
}
channel阻塞超時處理:
goroutine有時候會進入阻塞狀況,那麼如何避免因爲channel阻塞致使整個程序阻塞的發生那?解決方案:經過select設置超時處理,具體程序以下:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case i := <-c:
fmt.Println(i)
case <-time.After(time.Duration(3) * time.Second): //設置超時時間爲3s,若是channel 3s鐘沒有響應,一直阻塞,則報告超時,進行超時處理.
fmt.Println("timeout")
o <- true
break
}
}
}()
<-o
}
golang 併發總結: 併發兩種方式:sync.WaitGroup,該方法最大優勢是Wait()能夠阻塞到隊列中的全部任務都執行完才解除阻塞,可是它的缺點是不可以指定併發協程數量. channel優勢:可以利用帶緩存的channel指定併發協程goroutine,比較靈活.可是它的缺點是若是使用不當容易形成死鎖;而且他還須要本身斷定併發goroutine是否執行完. 可是相對而言,channel更加靈活,使用更加方便,同時經過超時處理機制能夠很好的避免channel形成的程序死鎖,所以利用channel實現程序併發,更加方便,更加易用.