聊聊Go的goroutine和Channel
- 前言
- 一、goroutine
- 定義
- 先看案例知道goroutine怎麼用
- 是什麼
- 二、channel
- 基礎用法
- 將channel當作參數傳遞
- 建立多個channel
- 將channel當作回傳值
- buffer channel
- channel關閉
相關文章推薦:《聊聊Go的並發程式設計(二)》
前言
##### #############在之前學習go語言時,看到groutine和channel時就直接跳過了。 ############當時根本沒當一回事,這麼複雜看它幹嘛! (當時的心態)############最近在看go的並發編程,發現全是用的這塊內容,那麼就只能硬著頭皮來了,但是你會發現看著看其實沒那麼難。 ######
有時候不想看的東西可以先放著,等自己的注意力集中後在進行查看,你會得到意想不到的收穫。
今天這篇文章是一個簡單的講解,咔咔也報了一個go的課程,在哪個課程裡邊看還能不能獲取到更多的理解,隨後在進行深度的補充。
- #在函數前加上go即可
- 不需要在定義是區分是否是非同步函數
- 調度器在適當的點切換,這個點是有很多的,這裡只是參考,不保證切換,不能保證在其它地方不會被切換。 IO操作、channel、等待鎖定、函數呼叫、runtime.Gosched()等。 。 。
- 使用race來偵測資料存取衝突
#
#這個案例就是一個簡單並發執行的程式碼,在go裡邊也就是一個關鍵字go即可。
從上圖可以看到這行程式碼什麼都沒有輸出,直接就退出了,那這到底是什麼情況呢?
直接退出的原因,就是因為我們程式碼中的main和fmt列印是並發執行的,fmt還沒來的急列印數據,外層的循環就已經循環結束了,然後就直接退出了。
在go語言呢!假設一個main函數退出後,會直接殺死所有的goroutine,所以就造成的現像是,goroutine還沒來的急列印資料就被退掉了。
那你是不是會想,要怎麼樣才能看到列印的資料呢?其實也很簡單,就是讓main函數執行完成之後不要急的退出,給一點等待的時間。看案例
在本案例中開的goroutine是10個,那麼改為1000會怎麼樣呢?
結果顯示還是正常顯示,就類似與有1000個人同時印東西。
對作業系統熟悉的應該都知道,開10個執行緒沒有問題,開100個執行緒也沒什麼大的問題,但已經差不多了。
一般系統開數十個執行緒就可以了,那麼如果要1000個人同時做一件事情就不能用執行緒來解決了,需要透過非同步方式。
但是在go語言呢!直接使用go關鍵字即可,就可以並發執行。
協程你可以理解為輕量級的執行緒
,非搶佔式多工處理,由協程主動交出控制權
。
線程大家應該都知道是可以被作業系統在任何時候進行切換,所以說線程就是搶佔式多任務處理,線程是沒有控制權,哪怕是一個語句執行到一半都會被作業系統切掉,然後轉到其它執行緒去操作。
那麼反之對協程來說,什麼時候交出控制權,什麼時候不交出控制權是由協程內部主動決定的,正是因為這種非搶佔式,所以稱為輕量級。
在第一節中了解到,在go中是可以開非常多的goroutine的,那麼goroutine之間的雙向通道就是channel
#從上圖案例中可以看到可以直接使用make函數來進行建立channel。
可以看到此時已經報錯了,錯誤的意思就是在往channel發送1的時候會發生死鎖。
在上文我們已經說了,channel是goroutine與goroutine之間的互動。
但是此時的案例中缺少只有一個goroutine,所以還需要一個另一個goroutine來接收它。
在上圖中我們新開啟了另一個goroutine,然後用了一個死循環來接受channel發送的值,並將其列印出來。
但你會發現我們往channel發送了兩個數據,此時的列印結果卻只有一條數據。但總比我們剛開始的好多了,對吧!
可以理理程式碼的執行流程,先往channel發送了一個1,然後循環取得到第一個值並列印。
再往channel發送資料2,但還沒來得及列印就直接退出了,這也就造成了只顯示了資料1而沒有顯示資料2的現象。
那就是給函數channelDome加一個延遲退出的時間即可。
在上文中可以看到go後邊跟的是一個閉包函數,在這個閉包中使用的c就是使用的外層的c。
那麼將這個c使用參數傳遞可否呢?答案是肯定可以的。
#
透過上圖可以看到不只傳遞了channel也傳遞了id參數,同時還可以將程式碼直接優化為圈住的部分,也就是直接從channel取值。
#從上圖可以看到每個人都有自己的channel,然後進行分發,分發之後每個人都會收到自己的接收到的值並列印出來。
同樣你可以看到我們在26行處還新加了一個for循環給channle裡邊發送資料。
從運行結果你會發現列印的順序是混亂的,例如receive i 和receve I這兩個值。
此時你會不會有疑問,我們在往channel中發送資料時是按照順序發送的啊!那麼接收時一定也是依照順序接收的。
既然非常確定發送資料是依照順序的,那麼問題就只能出現在Printf這裡。
因為Printf是存在IO的,goroutine進行調度,那麼此時的Printf是亂序的,但是都會將收到的值一一印出來。
前幾節的案例都是透過建立好的channle然後作為參數傳遞進去的。
那麼本節將會把channel當作一個回傳值給回傳。
#
package mainimport (
"fmt"
"time")func createWorker(id int) chan int {
c := make(chan int)
go func() {
for {
fmt.Printf("Worker %d receive %c\n", id,
從這裡你可以看到我們將worker函數改為了createWorker函數,因為在這個函數裡邊就是直接建立channel。
接著透過一個協程將channel接收到的值進行列印。
#
透過運行結果可以得知我們的程式碼編寫還是對的,但是此時回傳的channel你可以非常直覺的看到怎麼用
但如果程式碼數量多的時候,你根本不清楚這個channel怎麼用,這段程式碼還需要簡單的修飾一下。
透過上述程式碼可以得知,是往channel中發送資料的,那麼在createWorker
方法的回傳的channel要標記一下
#
所以說現在的程式碼就變成這個樣子,我們直接給createWorker方法的回傳值channel標記好方向。作用是送數據的。
那麼在列印的時候就是收據,這樣看起來就非常直觀了。
當修改完上面兩個步驟之後你會發現createWorker
呼叫是報錯了,Cannot use 'createWorker(i)' (type chan看到錯誤就應該知道是兩個邊型別不對等。
package mainimport (
"fmt"
"time")func createWorker(id int) chan
学习了这么久了,那么咔咔问你一个问题,这段代码执行会发生什么?
沒錯,會發生報錯,因為在文章開頭咔咔就講過了,給一個channel發送數據,就需要開啟另一個協程來收數據。
雖然協程我們說是輕量級的,但是如果發送了資料之後,就需要切換協程來進行收資料就非常的耗費資源。
建立了可以有3個緩衝區的channel,然後傳送3個資料到channel。
同時運行結果也可以得知沒有在發生deadlock
。
給你一個問題,如果往緩衝區在傳送一個資料4會發生什麼事呢?
聰明的你,一定就想到結果了,沒錯,報了deadlock
接著我們就使用之前的worker,來接受channel的資料。
但你會發現運行結果還是沒有印出送進去的1,2,3,4。
這個問題現在也已經說了好幾次了,你可以試著問一下你自己,這種情況你該怎麼解決。
也就是加上延遲時間即可,這裡順便給大家說明一下,之前的那個案例印的是字母,所有格式化的%c,現在印的是數字,所以改為了%d,一點小小的改動。
這種方式建立channel對效能的提升是有一定的作用的。
到現在有沒有發現一個問題,那就是在發送channel時不知道什麼時候發完了。
跟上節代碼不一致的是,我們在結尾處添加了close,close需要注意的是在發送方進行關閉的。
你會看到運行結果不如意,你會發現雖然收到了1,2,3,4。
但下面也接收了非常多的0,只是截圖只截到了一條資料而已。
雖然發送者將channel給close掉了,但是接受放也就是worker還是會收到資料的,不是說channel給close後就收不到資料了。
但是當發送方將channle設定為close之後,收到的資料就都是0,也就是收到的是worker方法傳遞的c chan int這個參數0的值。
現在我們的channel是一個int型,收到的是0。那如果是string類型,收到的就是一個空字串。
如果讓你改這段程式你有沒有思路呢?如果沒有思路就跟著這咔咔的節奏一起搖擺。
在函數worker中,使用兩個值來接收,n就是傳遞過來的channel c。 ok就是判斷這個值是否存在。
堅持學習、堅持寫作、堅持分享是咔咔從業以來一直所秉持的信念。希望在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是喀喀,下期見。
以上是聊聊Go的並發程式設計 (一)的詳細內容。更多資訊請關注PHP中文網其他相關文章!