您好,登錄后才能下訂單哦!
這篇文章給大家分享的是有關(guān)Go并發(fā)編程的示例分析的內(nèi)容。小編覺得挺實用的,因此分享給大家做個參考,一起跟隨小編過來看看吧。
給函數(shù)前加上go即可
不需要在定義是區(qū)分是否是異步函數(shù)
調(diào)度器在合適的點進(jìn)行切換,這個點是有很多的,這里只是參考,不保證切換,不能保證在其它地方不會被切換。IO操作、channel、等待鎖、函數(shù)調(diào)用、runtime.Gosched()等。。。
使用race來檢測數(shù)據(jù)訪問沖突
先來看一個案例
這個案例就是一個簡單并發(fā)執(zhí)行的代碼,在go里邊也就是一個關(guān)鍵字go即可。
那么來看一下這段代碼會輸出什么
從上圖可以看到這行代碼什么都沒有輸出,直接就退出了,那這到底是什么情況呢?
直接退出的原因,就是因為我們代碼中的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>
這次希望出現(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)行的
。
在第一節(jié)中了解到,在go中是可以開非常多的goroutine的,那么goroutine之間的雙向通道就是channel
從上圖案例中可以看到可以直接使用make函數(shù)來進(jìn)行創(chuàng)建channel。
第七行、第八行就是往channel中發(fā)送數(shù)據(jù)。
那么這個案例可以運(yùn)行嗎?來試一下
可以看到此時已經(jīng)報錯了,錯誤的意思就是在往channel發(fā)送1的時候會發(fā)生死鎖。
然后在回到之前的那副圖。
在上文我們已經(jīng)說了,channel是goroutine與goroutine之間的一個交互。
但是此時的案例中缺只有一個goroutine,所以還需要一個另一個goroutine來接收它。
現(xiàn)在你應(yīng)該了解到如何開啟一個goroutine了。
在上圖中我們新開啟了另一個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后邊跟的是一個閉包函數(shù),在這個閉包中使用的c就是使用的外層的c。
那么將這個c使用參數(shù)傳遞可否呢?答案是肯定可以的。
當(dāng)然也可以傳遞其它的參數(shù)
通過上圖可以看到不僅僅傳遞了channel還傳遞了id參數(shù),同時還可以將代碼直接優(yōu)化為圈住的部分,也就是直接從channel取值。
從上圖可以看到每個人都有自己的channel,然后進(jìn)行分發(fā),分發(fā)之后每個人都會收到自己的接收到的值并打印出來。
同樣你可以看到我們在26行處還新加了一個for循環(huán)給channle里邊發(fā)送數(shù)據(jù)。
從運(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是亂序的,但是都會將收到的值一一打印出來。
前幾節(jié)的案例都是通過創(chuàng)建好的channle然后作為參數(shù)傳遞進(jìn)去的。
那么本節(jié)將會把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, <-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é)果
通過運(yùn)行結(jié)果可以得知我們的代碼編寫還是對的,但是此時返回的channel你可以非常直觀的看到怎么用
但如果代碼數(shù)量多的時候,你根本不清楚這個channel怎么用,所有這段代碼還需要簡單的修飾一下。
那么就需要做的事情的是告訴外面用的人應(yīng)該怎么用。
通過上述代碼可以得知,是往channel中發(fā)送數(shù)據(jù)的,那么在createWorker
方法的返回的channel要標(biāo)記一下
所以說現(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)該知道是倆邊類型不對等。
修改完之后你就會發(fā)現(xiàn)編譯正確了,沒有報錯信息了。
運(yùn)行結(jié)果也是ok的。
本小節(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()}
學(xué)習(xí)了這么久了,那么咔咔問你一個問題,這段代碼執(zhí)行會發(fā)生什么?
沒錯,會發(fā)生報錯,因為在文章開頭咔咔就講過了,給一個channel發(fā)送數(shù)據(jù),就需要開啟另一個協(xié)程來收數(shù)據(jù)。
雖然協(xié)程我們說是輕量級的,但是如果發(fā)送了數(shù)據(jù)之后,就需要切換協(xié)程來進(jìn)行收數(shù)據(jù)就非常的耗費資源。
那么就是本節(jié)給大家講解的東西。
創(chuàng)建了可以有3個緩沖區(qū)的channel,然后往channel發(fā)送3個數(shù)據(jù)。
同時運(yùn)行結(jié)果也可以得知沒有在發(fā)生deadlock
。
給你一個問題,如果往緩沖區(qū)在發(fā)送一個數(shù)據(jù)4會發(fā)生什么呢?
聰明的你,肯定就想到結(jié)果了,沒錯,報了deadlock
接著我們就使用之前的worker,來接受channel的數(shù)據(jù)。
但是你會發(fā)現(xiàn)運(yùn)行結(jié)果還是沒有打印出送進(jìn)去的1,2,3,4。
這個問題現(xiàn)在也已經(jīng)說了好幾次了,你可以試著問一下你自己,這種情況你應(yīng)該怎么解決。
也就是加一個延遲時間即可,這里順便給大家說明一下,之前的那個案例打印的是字母,所有格式化用的%c,現(xiàn)在打印的是數(shù)字,所以改為了%d,一點小小的改動。
這種方式建立channel對性能的提升是有一定的作用的。
到現(xiàn)在你有沒有發(fā)現(xiàn)一個問題,那就是在發(fā)送channel時不知道什么時候發(fā)完了。
接下來就來看這個問題。
借用上個案例的代碼來繼續(xù)進(jìn)行說明。
跟上節(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é)奏一起搖擺。
在函數(shù)worker中,使用倆個值來進(jìn)行接收,n就是傳遞過來的channel c。ok就是判斷這個值是否存在。
可以看到運(yùn)行結(jié)果,就不會在出現(xiàn)接收到0的數(shù)據(jù)了。
除了這種寫法還有一種更簡單的方式。
感謝各位的閱讀!關(guān)于“Go并發(fā)編程的示例分析”這篇文章就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,讓大家可以學(xué)到更多知識,如果覺得文章不錯,可以把它分享出去讓更多的人看到吧!
免責(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)容。