溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

Go并發(fā)編程的示例分析

發(fā)布時間:2021-07-07 18:31:35 來源:億速云 閱讀:259 作者:小新 欄目:編程語言

這篇文章給大家分享的是有關(guān)Go并發(fā)編程的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。

一、goroutine

定義

  • 給函數(shù)前加上go即可

  • 不需要在定義是區(qū)分是否是異步函數(shù)

  • 調(diào)度器在合適的點進(jìn)行切換,這個點是有很多的,這里只是參考,不保證切換,不能保證在其它地方不會被切換。IO操作、channel、等待鎖、函數(shù)調(diào)用、runtime.Gosched()等。。。

  • 使用race來檢測數(shù)據(jù)訪問沖突

先看案例知道goroutine怎么用

先來看一個案例

Go并發(fā)編程的示例分析

這個案例就是一個簡單并發(fā)執(zhí)行的代碼,在go里邊也就是一個關(guān)鍵字go即可。

那么來看一下這段代碼會輸出什么

Go并發(fā)編程的示例分析

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

直接退出的原因,就是因為我們代碼中的main和fmt打印是并發(fā)執(zhí)行的,fmt還沒來的急打印數(shù)據(jù),外層的循環(huán)就已經(jīng)循環(huán)結(jié)束了,然后就直接退出了。

在go語言中呢!假設(shè)一個main函數(shù)退出后,會直接殺死所有的goroutine,所以就造成的現(xiàn)象是,goroutine還沒來的急打印數(shù)據(jù)就被退掉了。

那么你是不是會想,要怎么樣才能看到打印的數(shù)據(jù)呢?其實也很簡單,就是讓main函數(shù)執(zhí)行完成之后不要著急的退出,給一點等待的時間??窗咐?/p>

Go并發(fā)編程的示例分析

這次希望出現(xiàn)的結(jié)果就顯示出來了。

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

結(jié)果顯示還是正常顯示,就類似與有1000個人在同時打印東西。

那么設(shè)置10跟1000有什么關(guān)系嗎?

對操作系統(tǒng)熟悉的應(yīng)該都知道,開10個線程沒有問題,開100個線程也沒什么大的問題,但是已經(jīng)差不多了。

一般系統(tǒng)開幾十個線程就可以了,那么如果要1000個人同時做一件事情就不能用線程來解決了,需要通過異步方式。

但是在go語言中呢!直接使用go關(guān)鍵字即可,就可以并發(fā)執(zhí)行。

接下來就聊聊為什么go就可以同時1000進(jìn)行打印。

是什么

先來看看協(xié)程和線程的區(qū)別。

協(xié)程你可以理解為輕量級的線程,非搶占式多任務(wù)處理,由協(xié)程主動交出控制權(quán)。

線程大家應(yīng)該都知道是可以被操作系統(tǒng)在任何時候進(jìn)行切換,所以說線程就是搶占式多任務(wù)處理,線程是沒有控制權(quán),哪怕是一個語句執(zhí)行到一半都會被操作系統(tǒng)切掉,然后轉(zhuǎn)到其它線程去操作。

那么反之對于協(xié)程來說,什么時候交出控制權(quán),什么時候不交出控制權(quán)是由協(xié)程內(nèi)部主動決定的,正是因為這種非搶占式,所以被稱之為輕量級。

并且多個協(xié)程是可以在一個或多個線程上運(yùn)行的

二、channel

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

Go并發(fā)編程的示例分析

基礎(chǔ)用法

Go并發(fā)編程的示例分析

從上圖案例中可以看到可以直接使用make函數(shù)來進(jìn)行創(chuàng)建channel。

第七行、第八行就是往channel中發(fā)送數(shù)據(jù)。

那么這個案例可以運(yùn)行嗎?來試一下

Go并發(fā)編程的示例分析

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

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

Go并發(fā)編程的示例分析

在上文我們已經(jīng)說了,channel是goroutine與goroutine之間的一個交互。

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

現(xiàn)在你應(yīng)該了解到如何開啟一個goroutine了。

Go并發(fā)編程的示例分析

在上圖中我們新開啟了另一個goroutine,然后用了一個死循環(huán)來接受channel發(fā)送的值,并將其打印出來。

但是你會發(fā)現(xiàn)我們往channel中發(fā)送了倆個數(shù)據(jù),此時的打印結(jié)果卻只有一條數(shù)據(jù)。但總比我們剛開始的好多了,對吧!

那么為什么會發(fā)生這樣的情況呢?

可以理理代碼的執(zhí)行流程,先往channel發(fā)送了一個1,然后循環(huán)獲取到第一個值并打印。

再往channel發(fā)送數(shù)據(jù)2,但還沒來得及打印就直接退出了,這也就造成了只顯示了數(shù)據(jù)1而沒有顯示數(shù)據(jù)2的現(xiàn)象。

這個問題通過咔咔的描述你應(yīng)該已經(jīng)知道怎么解決了。

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

Go并發(fā)編程的示例分析

將channel作為參數(shù)傳遞

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

那么將這個c使用參數(shù)傳遞可否呢?答案是肯定可以的。

Go并發(fā)編程的示例分析

當(dāng)然也可以傳遞其它的參數(shù)

Go并發(fā)編程的示例分析

通過上圖可以看到不僅僅傳遞了channel還傳遞了id參數(shù),同時還可以將代碼直接優(yōu)化為圈住的部分,也就是直接從channel取值。

創(chuàng)建多個channel

Go并發(fā)編程的示例分析

從上圖可以看到每個人都有自己的channel,然后進(jìn)行分發(fā),分發(fā)之后每個人都會收到自己的接收到的值并打印出來。

同樣你可以看到我們在26行處還新加了一個for循環(huán)給channle里邊發(fā)送數(shù)據(jù)。

Go并發(fā)編程的示例分析

從運(yùn)行結(jié)果中你會發(fā)現(xiàn)打印的順序是混亂的,例如receive i 和receve I這倆個值。

此時你會不會有疑問,我們在往channel中發(fā)送數(shù)據(jù)時是按照順序發(fā)送的?。∧敲唇邮諘r肯定也是按照順序接收的。

既然非常確定發(fā)送數(shù)據(jù)是按照順序的,那么問題就只能出現(xiàn)在Printf這里。

因為Printf是存在IO的,goroutine進(jìn)行調(diào)度,那么此時的Printf是亂序的,但是都會將收到的值一一打印出來。

將channel作為返回值

前幾節(jié)的案例都是通過創(chuàng)建好的channle然后作為參數(shù)傳遞進(jìn)去的。

那么本節(jié)將會把channel作為一個返回值給返回出去。

Go并發(fā)編程的示例分析

源碼

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, <-c)
		}
	}()
	return c}func channelDemo() {
	var channels [10]chan int
	for i := 0; i < 10; i++ {
		channels[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i	}
	time.Sleep(time.Millisecond)}func main() {
	channelDemo()}

從這里你可以看到我們將worker函數(shù)改為了createWorker函數(shù),因為在這個函數(shù)里邊就是直接創(chuàng)建channel。

接著通過一個協(xié)程將channel接收到的值進(jìn)行打印。

在把channel進(jìn)行返回出去。

來看一下運(yùn)行結(jié)果

Go并發(fā)編程的示例分析

通過運(yùn)行結(jié)果可以得知我們的代碼編寫還是對的,但是此時返回的channel你可以非常直觀的看到怎么用

但如果代碼數(shù)量多的時候,你根本不清楚這個channel怎么用,所有這段代碼還需要簡單的修飾一下。

那么就需要做的事情的是告訴外面用的人應(yīng)該怎么用。

Go并發(fā)編程的示例分析

通過上述代碼可以得知,是往channel中發(fā)送數(shù)據(jù)的,那么在createWorker方法的返回的channel要標(biāo)記一下

Go并發(fā)編程的示例分析

所以說現(xiàn)在的代碼就變成這個樣子,我們直接給createWorker方法的返回值channel標(biāo)記好方向。作用是送數(shù)據(jù)的。

那么在打印的時候就是收據(jù),這樣看起來就非常直觀了。

當(dāng)修改完上面?zhèn)z步之后你會發(fā)現(xiàn)createWorker調(diào)用是報錯了,Cannot use 'createWorker(i)' (type chan<- int) as type chan int看到錯誤就應(yīng)該知道是倆邊類型不對等。

Go并發(fā)編程的示例分析

修改完之后你就會發(fā)現(xiàn)編譯正確了,沒有報錯信息了。

運(yùn)行結(jié)果也是ok的。

Go并發(fā)編程的示例分析

本小節(jié)源碼

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, <-c)
		}
	}()
	return c}func channelDemo() {
	var channels [10]chan<- int
	for i := 0; i < 10; i++ {
		channels[i] = createWorker(i)
	}

	for i := 0; i < 10; i++ {
		channels[i] <- 'a' + i	}
	time.Sleep(time.Millisecond)}func main() {
	channelDemo()}

buffer channel

學(xué)習(xí)了這么久了,那么咔咔問你一個問題,這段代碼執(zhí)行會發(fā)生什么?

Go并發(fā)編程的示例分析

沒錯,會發(fā)生報錯,因為在文章開頭咔咔就講過了,給一個channel發(fā)送數(shù)據(jù),就需要開啟另一個協(xié)程來收數(shù)據(jù)。

雖然協(xié)程我們說是輕量級的,但是如果發(fā)送了數(shù)據(jù)之后,就需要切換協(xié)程來進(jìn)行收數(shù)據(jù)就非常的耗費資源。

那么就是本節(jié)給大家講解的東西。

Go并發(fā)編程的示例分析

創(chuàng)建了可以有3個緩沖區(qū)的channel,然后往channel發(fā)送3個數(shù)據(jù)。

同時運(yùn)行結(jié)果也可以得知沒有在發(fā)生deadlock。

給你一個問題,如果往緩沖區(qū)在發(fā)送一個數(shù)據(jù)4會發(fā)生什么呢?

Go并發(fā)編程的示例分析

聰明的你,肯定就想到結(jié)果了,沒錯,報了deadlock

接著我們就使用之前的worker,來接受channel的數(shù)據(jù)。

Go并發(fā)編程的示例分析

但是你會發(fā)現(xiàn)運(yùn)行結(jié)果還是沒有打印出送進(jìn)去的1,2,3,4。

這個問題現(xiàn)在也已經(jīng)說了好幾次了,你可以試著問一下你自己,這種情況你應(yīng)該怎么解決。

Go并發(fā)編程的示例分析

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

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

到現(xiàn)在你有沒有發(fā)現(xiàn)一個問題,那就是在發(fā)送channel時不知道什么時候發(fā)完了。

接下來就來看這個問題。

channel關(guān)閉

借用上個案例的代碼來繼續(xù)進(jìn)行說明。

Go并發(fā)編程的示例分析
跟上節(jié)代碼不一致的是,我們在結(jié)尾處添加了close,close需要注意的是在發(fā)送方進(jìn)行關(guān)閉的。

你會看到運(yùn)行結(jié)果并不如意,你會發(fā)現(xiàn)雖然收到了1,2,3,4。

但是下面還接收到了非常多的0,只是截圖只截到了一條數(shù)據(jù)而已。

雖然發(fā)送方將channel給close掉了,但是接受放也就是worker還是會收到數(shù)據(jù)的,不是說channel給close后就收不到數(shù)據(jù)了。

但是當(dāng)發(fā)送方將channle設(shè)置為close之后,收到的數(shù)據(jù)就都是0,也就是收到的是worker方法傳遞的c chan int這個參數(shù)0的值。

現(xiàn)在我們的channel是一個int類型,收到的是0。那么如果是一個string類型,收到的就是一個空字符串。

這個會收多久呢?也就是咱們設(shè)置的一毫秒的時間。

如果讓你改這段程序你有沒有思路呢?如果沒有思路就跟這咔咔的節(jié)奏一起搖擺。

Go并發(fā)編程的示例分析

在函數(shù)worker中,使用倆個值來進(jìn)行接收,n就是傳遞過來的channel c。ok就是判斷這個值是否存在。

可以看到運(yùn)行結(jié)果,就不會在出現(xiàn)接收到0的數(shù)據(jù)了。

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

Go并發(fā)編程的示例分析

感謝各位的閱讀!關(guān)于“Go并發(fā)編程的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

go
AI