首頁 > 後端開發 > Golang > 聊聊Go的並發程式設計 (一)

聊聊Go的並發程式設計 (一)

咔咔
發布: 2021-07-07 16:36:55
原創
2230 人瀏覽過

聊聊Go的goroutine和Channel

  • 前言
  • 一、goroutine
    • 定義
    • 先看案例知道goroutine怎麼用
    • 是什麼
  • 二、channel
    • 基礎用法
    • 將channel當作參數傳遞
    • 建立多個channel
    • 將channel當作回傳值
    • buffer channel
    • channel關閉

    相關文章推薦:《聊聊Go的並發程式設計(二)

    前言

##### #############在之前學習go語言時,看到groutine和channel時就直接跳過了。 ############當時根本沒當一回事,這麼複雜看它幹嘛! (當時的心態)############最近在看go的並發編程,發現全是用的這塊內容,那麼就只能硬著頭皮來了,但是你會發現看著看其實沒那麼難。 ######

有時候不想看的東西可以先放著,等自己的注意力集中後在進行查看,你會得到意想不到的收穫。

今天這篇文章是一個簡單的講解,咔咔也報了一個go的課程,在哪個課程裡邊看還能不能獲取到更多的理解,隨後在進行深度的補充。

一、goroutine

  • #在函數前加上go即可
  • 不需要在定義是區分是否是非同步函數
  • 調度器在適當的點切換,這個點是有很多的,這裡只是參考,不保證切換,不能保證在其它地方不會被切換。 IO操作、channel、等待鎖定、函數呼叫、runtime.Gosched()等。 。 。
  • 使用race來偵測資料存取衝突

#先看案例知道goroutine怎麼用

#

先來看一個案例

聊聊Go的並發程式設計 (一)

#這個案例就是一個簡單並發執行的程式碼,在go裡邊也就是一個關鍵字go即可。

那麼來看這段程式碼會輸出什麼

聊聊Go的並發程式設計 (一)

從上圖可以看到這行程式碼什麼都沒有輸出,直接就退出了,那這到底是什麼情況呢?

直接退出的原因,就是因為我們程式碼中的main和fmt列印是並發執行的,fmt還沒來的急列印數據,外層的循環就已經循環結束了,然後就直接退出了。

在go語言呢!假設一個main函數退出後,會直接殺死所有的goroutine,所以就造成的現像是,goroutine還沒來的急列印資料就被退掉了。

那你是不是會想,要怎麼樣才能看到列印的資料呢?其實也很簡單,就是讓main函數執行完成之後不要急的退出,給一點等待的時間。看案例

聊聊Go的並發程式設計 (一)

這次希望出現的結果就顯示出來了。

在本案例中開的goroutine是10個,那麼改為1000會怎麼樣呢?

結果顯示還是正常顯示,就類似與有1000個人同時印東西。

那麼設定10跟1000有什麼關係嗎?

對作業系統熟悉的應該都知道,開10個執行緒沒有問題,開100個執行緒也沒什麼大的問題,但已經差不多了。

一般系統開數十個執行緒就可以了,那麼如果要1000個人同時做一件事情就不能用執行緒來解決了,需要透過非同步方式。

但是在go語言呢!直接使用go關鍵字即可,就可以並發執行。

接下來就聊聊為什麼go可以同時1000列印。

是什麼

#先來看看協程和執行緒的差別。

協程你可以理解為輕量級的執行緒非搶佔式多工處理,由協程主動交出控制權

線程大家應該都知道是可以被作業系統在任何時候進行切換,所以說線程就​​是搶佔式多任務處理,線程是沒有控制權,哪怕是一個語句執行到一半都會被作業系統切掉,然後轉到其它執行緒去操作。

那麼反之對協程來說,什麼時候交出控制權,什麼時候不交出控制權是由協程內部主動決定的,正是因為這種非搶佔式,所以稱為輕量級。

多個協程是可以在一個或多個執行緒上執行的

二、channel

在第一節中了解到,在go中是可以開非常多的goroutine的,那麼goroutine之間的雙向通道就是channel

聊聊Go的並發程式設計 (一)

基礎用法

聊聊Go的並發程式設計 (一)

#從上圖案例中可以看到可以直接使用make函數來進行建立channel。

第七行、第八行就是要傳送資料到channel。

那麼這個案例可以運行嗎?來試試

聊聊Go的並發程式設計 (一)

可以看到此時已經報錯了,錯誤的意思就是在往channel發送1的時候會發生死鎖。

然後在回到之前的那副圖。

聊聊Go的並發程式設計 (一)

在上文我們已經說了,channel是goroutine與goroutine之間的互動。

但是此時的案例中缺少只有一個goroutine,所以還需要一個另一個goroutine來接收它。

現在你應該了解如何開啟一個goroutine了。

聊聊Go的並發程式設計 (一)

在上圖中我們新開啟了另一個goroutine,然後用了一個死循環來接受channel發送的值,並將其列印出來。

但你會發現我們往channel發送了兩個數據,此時的列印結果卻只有一條數據。但總比我們剛開始的好多了,對吧!

那為什麼會發生這樣的情況呢?

可以理理程式碼的執行流程,先往channel發送了一個1,然後循環取得到第一個值並列印。

再往channel發送資料2,但還沒來得及列印就直接退出了,這也就造成了只顯示了資料1而沒有顯示資料2的現象。

這個問題透過咔咔的描述你應該已經知道怎麼解決了。

那就是給函數channelDome加一個延遲退出的時間即可。

聊聊Go的並發程式設計 (一)

#將channel當作參數傳遞

在上文中可以看到go後邊跟的是一個閉包函數,在這個閉包中使用的c就是使用的外層的c。

那麼將這個c使用參數傳遞可否呢?答案是肯定可以的。

聊聊Go的並發程式設計 (一)

當然也可以傳遞其它的參數

#

聊聊Go的並發程式設計 (一)

透過上圖可以看到不只傳遞了channel也傳遞了id參數,同時還可以將程式碼直接優化為圈住的部分,也就是直接從channel取值。

建立多個channel

聊聊Go的並發程式設計 (一)

#從上圖可以看到每個人都有自己的channel,然後進行分發,分發之後每個人都會收到自己的接收到的值並列印出來。

同樣你可以看到我們在26行處還新加了一個for循環給channle裡邊發送資料。

聊聊Go的並發程式設計 (一)

從運行結果你會發現列印的順序是混亂的,例如receive i 和receve I這兩個值。

此時你會不會有疑問,我們在往channel中發送資料時是按照順序發送的啊!那麼接收時一定也是依照順序接​​收的。

既然非常確定發送資料是依照順序的,那麼問題就只能出現在Printf這裡。

因為Printf是存在IO的,goroutine進行調度,那麼此時的Printf是亂序的,但是都會將收到的值一一印出來。

將channel當作傳回值

前幾節的案例都是透過建立好的channle然後作為參數傳遞進去的。

那麼本節將會把channel當作一個回傳值給回傳。

聊聊Go的並發程式設計 (一)

原始碼

#
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進行回傳。

來看一下運行結果

聊聊Go的並發程式設計 (一)

#

透過運行結果可以得知我們的程式碼編寫還是對的,但是此時回傳的channel你可以非常直覺的看到怎麼用

但如果程式碼數量多的時候,你根本不清楚這個channel怎麼用,這段程式碼還需要簡單的修飾一下。

那麼就需要做的事情的是告訴外面用的人應該怎麼做。

聊聊Go的並發程式設計 (一)

透過上述程式碼可以得知,是往channel中發送資料的,那麼在createWorker方法的回傳的channel要標記一下

聊聊Go的並發程式設計 (一)

#

所以說現在的程式碼就變成這個樣子,我們直接給createWorker方法的回傳值channel標記好方向。作用是送數據的。

那麼在列印的時候就是收據,這樣看起來就非常直觀了。

當修改完上面兩個步驟之後你會發現createWorker呼叫是報錯了,Cannot use 'createWorker(i)' (type chan看到錯誤就應該知道是兩個邊型別不對等。

聊聊Go的並發程式設計 (一)

修改完之後你會發現編譯正確了,沒有報錯訊息了。

聊聊Go的並發程式設計 (一)也是ok的。

聊聊Go的並發程式設計 (一)

本小节源码

package mainimport (
	"fmt"
	"time")func createWorker(id int) chan
登入後複製

buffer channel

学习了这么久了,那么咔咔问你一个问题,这段代码执行会发生什么?

聊聊Go的並發程式設計 (一)

沒錯,會發生報錯,因為在文章開頭咔咔就講過了,給一個channel發送數據,就需要開啟另一個協程來收數據。

雖然協程我們說是輕量級的,但是如果發送了資料之後,就需要切換協程來進行收資料就非常的耗費資源。

那麼就是本節要跟大家講解的東西。

buffer channel

建立了可以有3個緩衝區的channel,然後傳送3個資料到channel。

同時運行結果也可以得知沒有在發生deadlock

給你一個問題,如果往緩衝區在傳送一個資料4會發生什麼事呢?

聊聊Go的並發程式設計 (一)

聰明的你,一定就想到結果了,沒錯,報了deadlock

接著我們就使用之前的worker,來接受channel的資料。

聊聊Go的並發程式設計 (一)

但你會發現運行結果還是沒有印出送進去的1,2,3,4。

這個問題現在也已經說了好幾次了,你可以試著問一下你自己,這種情況你該怎麼解決。

聊聊Go的並發程式設計 (一)

也就是加上延遲時間即可,這裡順便給大家說明一下,之前的那個案例印的是字母,所有格式化的%c,現在印的是數字,所以改為了%d,一點小小的改動。

這種方式建立channel對效能的提升是有一定的作用的。

到現在有沒有發現一個問題,那就是在發送channel時不知道什麼時候發完了。

接下來就來看這個問題。

channel關閉

借用上個案例的程式碼來繼續進行說明。

聊聊Go的並發程式設計 (一)
跟上節代碼不一致的是,我們在結尾處添加了close,close需要注意的是在發送方進行關閉的。

你會看到運行結果不如意,你會發現雖然收到了1,2,3,4。

但下面也接收了非常多的0,只是截圖只截到了一條資料而已。

雖然發送者將channel給close掉了,但是接受放也就是worker還是會收到資料的,不是說channel給close後就收不到資料了。

但是當發送方將channle設定為close之後,收到的資料就都是0,也就是收到的是worker方法傳遞的c chan int這個參數0的值。

現在我們的channel是一個int型,收到的是0。那如果是string類型,收到的就是一個空字串。

這個會收多久呢?也就是咱們設定的一毫秒的時間。

如果讓你改這段程式你有沒有思路呢?如果沒有思路就跟著這咔咔的節奏一起搖擺。

聊聊Go的並發程式設計 (一)

在函數worker中,使用兩個值來接收,n就是傳遞過來的channel c。 ok就是判斷這個值是否存在。

可以看到運行結果,就不會在出現接收到0的資料了。

除了這種寫法還有一種更簡單的方式。

聊聊Go的並發程式設計 (一)

堅持學習、堅持寫作、堅持分享是咔咔從業以來一直所秉持的信念。希望在偌大互聯網中咔咔的文章能帶給你一絲絲幫助。我是喀喀,下期見。

以上是聊聊Go的並發程式設計 (一)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

相關標籤:
go
來源:php.cn
本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
熱門教學
更多>
最新下載
更多>
網站特效
網站源碼
網站素材
前端模板