golang中並發sync和channel

來源:互聯網
上載者:User

golang中實現並發非常簡單,只需在需要並發的函數前面添加關鍵字"go",但是如何處理go並發機制中不同goroutine之間的同步與通訊,golang 中提供了sync包和channel機制來解決這一問題.

sync 包提供了互斥鎖這類的基本的同步原語.除 Once 和 WaitGroup 之外的類型大多用於底層庫的常式。更進階的同步操作通過通道與通訊進行。

 

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 mainimport ("fmt""sync")var waitgroup sync.WaitGroupfunc Afunction(shownum int) {fmt.Println(shownum)waitgroup.Done() //任務完成,將任務隊列中的任務數量-1,其實.Done就是.Add(-1)}func main() {for i := 0; i < 10; i++ {waitgroup.Add(1) //每建立一個goroutine,就把任務隊列中任務的數量+1go Afunction(i)}waitgroup.Wait() //.Wait()這裡會發生阻塞,直到隊列中所有的任務結束就會解除阻塞}
使用情境:

 

  程式中需要並發,需要建立多個goroutine,並且一定要等這些並發全部完成後才繼續接下來的程式執行.WaitGroup的特點是Wait()可以用來阻塞直到隊列中的所有任務都完成時才解除阻塞,而不需要sleep一個固定的時間來等待.但是其缺點是無法指定固定的goroutine數目.

 

 

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 mainimport "fmt"func Afuntion(ch chan int) {fmt.Println("finish")<-ch}func main() {ch := make(chan int) //無緩衝的channelgo Afuntion(ch)ch <- 1// 輸出結果:// finish}

 

程式碼分析:首先建立一個無緩衝channel ch, 然後執行 go Afuntion(ch),此時執行<-ch,則Afuntion這個函數便會阻塞,不再繼續往下執行,直到主進程中ch<-1向channel ch 中注入資料才解除Afuntion該協程的阻塞.

 

package mainimport "fmt"func Afuntion(ch chan int) {fmt.Println("finish")<-ch}func main() {ch := make(chan int) //無緩衝的channel//只是把這兩行的代碼順序對調一下ch <- 1go 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 mainimport "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實現程式並發,更加方便,更加易用.


參考文獻:

http://studygolang.com/articles/319

http://studygolang.com/articles/267

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.