Golang並發編程

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。## Goroutine在Go語言中,語言本身就已經實現和支援了並發, 我們只需要通過`go`關鍵字來開啟`goroutine`即可。gouroutine其實就是一種協程,類似其他語言中的coroutine, 是在編譯器或虛擬機器層面上的多任務。它可以運行在一個或多個線程上,但不同於線程,它是**非搶佔式的**,所以協程很輕量。```func main() { for i := 0; i < 1000; i++ { go func(ii int) { for { fmt.Printf("Hello %d\n", ii) } }(i) } time.Sleep(time.Millisecond)}```上述代碼就開啟了1000個協程,在1ms內不斷的列印字串,這裡需要注意兩個點:1. time.Sleep 在main函數退出前,Sleep了1ms。這是因為當main函數退出時,之前開的協程也會隨著退出,如果不Sleep,則無法看到列印資訊。2. 匿名函數將變數i作為參數賦值傳入。 如果不傳參,變數i也能被使用,但是是以引用的方式。而i在main函數中在不斷自增,導致在goroutine列印資訊中,無法知道是第幾個協程列印的。從列印資訊上看,跟開線程沒什麼區別,無非就是數量上不同。但是在作業系統層面,線程是搶佔式,而我們之前說協程是非搶佔式的,這怎麼會一樣呢?<!-- more -->出現上述問題的原因在於,在調用`Printf`的時候,進行了切換, goroutine主動讓出了控制權。我們修改代碼如下,示範下非搶佔:```a := [10]int{}for i := 0; i < 10; i++ { go func(ii int) { for { a[ii]++ } }(i)}time.Sleep(time.Millisecond)fmt.Println(a)```運行上述代碼,出現了死迴圈。因為在開闢的第一個goroutine中,一直迴圈執行`a[ii]++`,一直沒有讓出控制權;而`main`本質上也是個goroutine,所以後面的代碼都沒有執行完,也沒有退出。遇到這種情況,我們可以在goroutine中主動讓出控制權,例如:```a[ii]++runtime.Gosched()```goroutine 可能會切換的點 (不能保證):* I/O,select* channel* 等待鎖* runtime.Gosched()## CSP並行存取模型Go實現了兩種並發形式:1. 共用記憶體 + 鎖同步2. CSP. 通過goroutine和channel來實現的.CSP並行存取模型是在1970年左右提出的概念,屬於比較新的概念,不同於傳統的多線程通過共用記憶體來通訊,CSP講究的是“以通訊的方式來共用記憶體”。>Do not communicate by sharing memory; instead, share memory by communicating>不要以共用記憶體的方式來通訊,相反,要通過通訊來共用記憶體。## channelchannel 是用來在不同goroutine之間進行通訊的,無論傳值還是取值, 它都是阻塞的。```c := make(chan int)c <- 1```上面代碼直接運行會造成死結:```all goroutines are asleep - deadlock!```所以一般在使用channel前先開一個goroutine去接收channel:```func createWorker() chan int { c := make(chan int) go func() { for n := range c { fmt.Println("received:", n) } }() return c}func main() { var channels [10]chan int for i, _ := range channels { channels[i] = createWorker() } for i, c := range channels { c <- i } time.Sleep(time.Millisecond)}```在上述代碼中,我們定義了一個`createWorker`,用來建立一個接收者,同時返回了一個channel。同時我們可以對返回的channel做限制,例如:```func createWorker() chan<- int // 只能發送資料func createWorker() <-chan int // 只能接收資料```一般可以通過`n := <- c`來接收資料,在上述例子中使用了range,因為channel是可以close的。`close(c)`關閉channel, 但是關閉後在worker中依然能接收到channel(只要goroutine沒有退出)。而接收到的資料是定義的channel的零值,在上述例子中,則收到0.* 通過`n,ok := <- c`的ok來判斷channel是否關閉;也可以通過range來接收;* 如果往已經關閉的channel寫資料,會panic:`send on closed channel`. **不要從接收端關閉channel,也不要關閉有多個並發寄件者的channel**### 等待任務結束在之前的例子中,我們都是通過Sleep方法來粗略的控制任務的執行,這在實際生產中肯定不能這麼幹。之前也說了channel是用來通訊的,那麼我們可以通過channel來告訴使用者任務已經執行完了。 代碼最佳化如下:```type worker struct { in chan int done chan bool}func createWorker() worker { w := worker{ in: make(chan int), done: make(chan bool), } go func(w worker) { for n := range w.in { fmt.Println("received:", n) w.done <- true } }(w) return w}func chanNormal() { var workers [10]worker for i, _ := range workers { workers[i] = createWorker() } for i, w := range workers { w.in <- i } for _, w := range workers { <-w.done }}func main() { chanNormal()}```除了我們自己定義channel,go也為我們提供了`sync.WaitGroup`,來管理一組任務。```var wg sync.WaitGroupwg.Add(1)wg.Done()wg.Wait()```### Tip將struct中的done抽象成一個方法,在`create`的時候實現,這樣在`worker`中就不用管具體代碼了,只要調用done方法即可。## 原文[連結](http://blog.lichfaker.com/2018/02/27/Golang%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/)602 次點擊  

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.