91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

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

Go并發編程的示例分析

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

這篇文章給大家分享的是有關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, <-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函數改為了createWorker函數,因為在這個函數里邊就是直接創建channel。

接著通過一個協程將channel接收到的值進行打印。

在把channel進行返回出去。

來看一下運行結果

Go并發編程的示例分析

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

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

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

Go并發編程的示例分析

通過上述代碼可以得知,是往channel中發送數據的,那么在createWorker方法的返回的channel要標記一下

Go并發編程的示例分析

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

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

當修改完上面倆步之后你會發現createWorker調用是報錯了,Cannot use 'createWorker(i)' (type chan<- int) as type chan int看到錯誤就應該知道是倆邊類型不對等。

Go并發編程的示例分析

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

運行結果也是ok的。

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, <-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

學習了這么久了,那么咔咔問你一個問題,這段代碼執行會發生什么?

Go并發編程的示例分析

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

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

那么就是本節給大家講解的東西。

Go并發編程的示例分析

創建了可以有3個緩沖區的channel,然后往channel發送3個數據。

同時運行結果也可以得知沒有在發生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并發編程的示例分析”這篇文章就分享到這里了,希望以上內容可以對大家有一定的幫助,讓大家可以學到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

go
AI

南江县| 铁力市| 玉林市| 昌吉市| 交口县| 策勒县| 岑溪市| 浦江县| 大理市| 确山县| 陇川县| 寻乌县| 上栗县| 永福县| 威信县| 永仁县| 米泉市| 墨玉县| 嘉峪关市| 积石山| 凌云县| 景宁| 彰武县| 苏尼特右旗| 长泰县| 德清县| 临江市| 绥棱县| 黔西| 察隅县| 武胜县| 丰城市| 榆社县| 六盘水市| 孝感市| 林西县| 新蔡县| 博野县| 内乡县| 麦盖提县| 安阳市|