真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

go語言開啟協(xié)程關(guān)鍵字,go 協(xié)程通信

channel使用

【譯文】 原文地址

成都創(chuàng)新互聯(lián)公司專注于紅河哈尼網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠為您提供紅河哈尼營銷型網(wǎng)站建設(shè),紅河哈尼網(wǎng)站制作、紅河哈尼網(wǎng)頁設(shè)計(jì)、紅河哈尼網(wǎng)站官網(wǎng)定制、成都微信小程序服務(wù),打造紅河哈尼網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供紅河哈尼網(wǎng)站排名全網(wǎng)營銷落地服務(wù)。

channel是Go語言的一個(gè)標(biāo)志性特性,為go協(xié)程之間的數(shù)據(jù)交互提供一種非常強(qiáng)大的方式,而不需要使用鎖機(jī)制。

本文將討論channel的兩個(gè)重要屬性,一個(gè)是控制協(xié)程間數(shù)據(jù)發(fā)送和接收,以及對(duì)channel本身控制。

首先討論下關(guān)閉的channel特性。一旦channel被關(guān)閉之后,就不能再繼續(xù)發(fā)送數(shù)據(jù)給該channel,但是還是可以繼續(xù)接收channel中的數(shù)據(jù)。如下所示:

output:

上述例子顯示即使ch在for循環(huán)之前已經(jīng)關(guān)閉,但還是可以正常的讀取緩存中的true值,讀完之后ok就會(huì)被賦值為false表示channel已經(jīng)關(guān)閉,而且value值為對(duì)應(yīng)channel類型bool的默認(rèn)零值false。只要不停地從關(guān)閉的channel接收,就會(huì)無限的返回默認(rèn)值和false??梢詫or循環(huán)次數(shù)改大點(diǎn)試試即可驗(yàn)證。

通過以上例子可以發(fā)現(xiàn),關(guān)閉的channel可以繼續(xù)接收讀取操作,這種特征是有用的。在使用range讀取帶緩存的channel時(shí)就會(huì)用到,一旦channel關(guān)閉,讀取完緩存中數(shù)據(jù)就會(huì)停止接收數(shù)據(jù)退出。

將前面的例子改為如下:

output:

上面的例子就沒有false打出來了。正好是寫入channel里面的兩個(gè)值。

channel與select結(jié)合更能發(fā)揮出其作用,讓我們看一個(gè)例子:

上面的例子,因?yàn)閒inish在主協(xié)程中發(fā)送之后,馬上就會(huì)在select中接收,并執(zhí)行done.Done()。主協(xié)程wait馬上會(huì)退出整個(gè)程序就結(jié)束。但是這里面存在一個(gè)問題,如果在select中沒有添加finish case的話,主協(xié)程就永遠(yuǎn)發(fā)送不了數(shù)據(jù)到finish這個(gè)channel,因?yàn)槠洳粠Ь彺?。這里就可以通過將finish改成帶緩存的channel,或者可以讓select中的finish不會(huì)阻塞。

但是出現(xiàn)多個(gè)協(xié)程都在接收finish通道中的數(shù)據(jù)的話,就需要發(fā)送對(duì)應(yīng)協(xié)程數(shù)量的值到channel中才能解決上面的問題。但是具體有多少個(gè)協(xié)程這往往是不好確定的,因?yàn)橛行﹨f(xié)程可能是程序其他部分創(chuàng)建的。一個(gè)比較好的選擇就是通過使用關(guān)閉通道的方法來實(shí)現(xiàn)各協(xié)程能正常接收并結(jié)束。

如下所示:

output:

上面的例子就是使用了關(guān)閉的channel可以無限地接收到反饋數(shù)據(jù)。這樣每個(gè)協(xié)程都能從finish通道中讀到關(guān)閉信息并執(zhí)行done.Done()使得主協(xié)程wait能退出。并且不需要關(guān)注多少個(gè)協(xié)程數(shù),就能正確的讓所有協(xié)程讀到finish通道信息。

channel的這個(gè)特性,可以讓程序員無需關(guān)注后臺(tái)具體執(zhí)行協(xié)程個(gè)數(shù),確保每個(gè)協(xié)程都能接收到通道關(guān)閉信息,而無需擔(dān)心死鎖問題。

通過上面的例子我們也發(fā)現(xiàn)每個(gè)協(xié)程并不需要從通道中讀取對(duì)應(yīng)類型的數(shù)據(jù),只需讓接收操作能執(zhí)行就行,讓select不被阻塞。所以可以使用空結(jié)構(gòu)體類型,我們可以改成如下:

這里我們只關(guān)注通道是否關(guān)閉這個(gè)信號(hào),而不需要關(guān)注通道里面的數(shù)據(jù),所以可使用空結(jié)構(gòu)體類型通道。

第二個(gè)要討論的是nil通道:如果定義了一個(gè)channel變量沒有被初始化,或者被賦值為nil,那么該chennel總是處于阻塞狀態(tài)。如下所示:

執(zhí)行結(jié)果為:

因?yàn)閏hannel為nil無法發(fā)送數(shù)據(jù),當(dāng)然也不能接收數(shù)據(jù):

這個(gè)似乎看起來不是很重要,但是如果你想使用關(guān)閉channel來等待多個(gè)channel關(guān)閉的話,這個(gè)特性就有用處了。先看下面的例子:

WaitMany()函數(shù)看起來好像是一個(gè)等待通道a和b關(guān)閉的好方法,但是存在一個(gè)問題。假設(shè)a通道先關(guān)閉,case -a就會(huì)變成非阻塞。因?yàn)閎closed還是false,程序就會(huì)進(jìn)入到一個(gè)死循環(huán)當(dāng)中,導(dǎo)致b通道永遠(yuǎn)無法確認(rèn)關(guān)閉。

一個(gè)安全的方法就是使用nil通道總是阻塞的特點(diǎn),如下所示:

上面的例子我們?cè)赪aitMany函數(shù)當(dāng)中,當(dāng)a或者b關(guān)閉時(shí),case可執(zhí)行了將對(duì)應(yīng)的通道賦值為nil,讓其阻塞這樣就可以等待另一個(gè)通道關(guān)閉。當(dāng)nil通道是select語句的一部分時(shí),它會(huì)被有效地忽略,因此nil通道a會(huì)從select中刪除它,只留下b,直到它被關(guān)閉,退出循環(huán)。

總之,closed和nil通道的簡單屬性對(duì)寫出優(yōu)質(zhì)的go程序是很有用的,可以用來創(chuàng)建高并發(fā)程序。

【golang詳解】go語言GMP(GPM)原理和調(diào)度

Goroutine調(diào)度是一個(gè)很復(fù)雜的機(jī)制,下面嘗試用簡單的語言描述一下Goroutine調(diào)度機(jī)制,想要對(duì)其有更深入的了解可以去研讀一下源碼。

首先介紹一下GMP什么意思:

G ----------- goroutine: 即Go協(xié)程,每個(gè)go關(guān)鍵字都會(huì)創(chuàng)建一個(gè)協(xié)程。

M ---------- thread內(nèi)核級(jí)線程,所有的G都要放在M上才能運(yùn)行。

P ----------- processor處理器,調(diào)度G到M上,其維護(hù)了一個(gè)隊(duì)列,存儲(chǔ)了所有需要它來調(diào)度的G。

Goroutine 調(diào)度器P和 OS 調(diào)度器是通過 M 結(jié)合起來的,每個(gè) M 都代表了 1 個(gè)內(nèi)核線程,OS 調(diào)度器負(fù)責(zé)把內(nèi)核線程分配到 CPU 的核上執(zhí)行

模型圖:

避免頻繁的創(chuàng)建、銷毀線程,而是對(duì)線程的復(fù)用。

1)work stealing機(jī)制

當(dāng)本線程無可運(yùn)行的G時(shí),嘗試從其他線程綁定的P偷取G,而不是銷毀線程。

2)hand off機(jī)制

當(dāng)本線程M0因?yàn)镚0進(jìn)行系統(tǒng)調(diào)用阻塞時(shí),線程釋放綁定的P,把P轉(zhuǎn)移給其他空閑的線程執(zhí)行。進(jìn)而某個(gè)空閑的M1獲取P,繼續(xù)執(zhí)行P隊(duì)列中剩下的G。而M0由于陷入系統(tǒng)調(diào)用而進(jìn)被阻塞,M1接替M0的工作,只要P不空閑,就可以保證充分利用CPU。M1的來源有可能是M的緩存池,也可能是新建的。當(dāng)G0系統(tǒng)調(diào)用結(jié)束后,根據(jù)M0是否能獲取到P,將會(huì)將G0做不同的處理:

如果有空閑的P,則獲取一個(gè)P,繼續(xù)執(zhí)行G0。

如果沒有空閑的P,則將G0放入全局隊(duì)列,等待被其他的P調(diào)度。然后M0將進(jìn)入緩存池睡眠。

如下圖

GOMAXPROCS設(shè)置P的數(shù)量,最多有GOMAXPROCS個(gè)線程分布在多個(gè)CPU上同時(shí)運(yùn)行

在Go中一個(gè)goroutine最多占用CPU 10ms,防止其他goroutine被餓死。

具體可以去看另一篇文章

【Golang詳解】go語言調(diào)度機(jī)制 搶占式調(diào)度

當(dāng)創(chuàng)建一個(gè)新的G之后優(yōu)先加入本地隊(duì)列,如果本地隊(duì)列滿了,會(huì)將本地隊(duì)列的G移動(dòng)到全局隊(duì)列里面,當(dāng)M執(zhí)行work stealing從其他P偷不到G時(shí),它可以從全局G隊(duì)列獲取G。

協(xié)程經(jīng)歷過程

我們創(chuàng)建一個(gè)協(xié)程 go func()經(jīng)歷過程如下圖:

說明:

這里有兩個(gè)存儲(chǔ)G的隊(duì)列,一個(gè)是局部調(diào)度器P的本地隊(duì)列、一個(gè)是全局G隊(duì)列。新創(chuàng)建的G會(huì)先保存在P的本地隊(duì)列中,如果P的本地隊(duì)列已經(jīng)滿了就會(huì)保存在全局的隊(duì)列中;處理器本地隊(duì)列是一個(gè)使用數(shù)組構(gòu)成的環(huán)形鏈表,它最多可以存儲(chǔ) 256 個(gè)待執(zhí)行任務(wù)。

G只能運(yùn)行在M中,一個(gè)M必須持有一個(gè)P,M與P是1:1的關(guān)系。M會(huì)從P的本地隊(duì)列彈出一個(gè)可執(zhí)行狀態(tài)的G來執(zhí)行,如果P的本地隊(duì)列為空,就會(huì)想其他的MP組合偷取一個(gè)可執(zhí)行的G來執(zhí)行;

一個(gè)M調(diào)度G執(zhí)行的過程是一個(gè)循環(huán)機(jī)制;會(huì)一直從本地隊(duì)列或全局隊(duì)列中獲取G

上面說到P的個(gè)數(shù)默認(rèn)等于CPU核數(shù),每個(gè)M必須持有一個(gè)P才可以執(zhí)行G,一般情況下M的個(gè)數(shù)會(huì)略大于P的個(gè)數(shù),這多出來的M將會(huì)在G產(chǎn)生系統(tǒng)調(diào)用時(shí)發(fā)揮作用。類似線程池,Go也提供一個(gè)M的池子,需要時(shí)從池子中獲取,用完放回池子,不夠用時(shí)就再創(chuàng)建一個(gè)。

work-stealing調(diào)度算法:當(dāng)M執(zhí)行完了當(dāng)前P的本地隊(duì)列隊(duì)列里的所有G后,P也不會(huì)就這么在那躺尸啥都不干,它會(huì)先嘗試從全局隊(duì)列隊(duì)列尋找G來執(zhí)行,如果全局隊(duì)列為空,它會(huì)隨機(jī)挑選另外一個(gè)P,從它的隊(duì)列里中拿走一半的G到自己的隊(duì)列中執(zhí)行。

如果一切正常,調(diào)度器會(huì)以上述的那種方式順暢地運(yùn)行,但這個(gè)世界沒這么美好,總有意外發(fā)生,以下分析goroutine在兩種例外情況下的行為。

Go runtime會(huì)在下面的goroutine被阻塞的情況下運(yùn)行另外一個(gè)goroutine:

用戶態(tài)阻塞/喚醒

當(dāng)goroutine因?yàn)閏hannel操作或者network I/O而阻塞時(shí)(實(shí)際上golang已經(jīng)用netpoller實(shí)現(xiàn)了goroutine網(wǎng)絡(luò)I/O阻塞不會(huì)導(dǎo)致M被阻塞,僅阻塞G,這里僅僅是舉個(gè)栗子),對(duì)應(yīng)的G會(huì)被放置到某個(gè)wait隊(duì)列(如channel的waitq),該G的狀態(tài)由_Gruning變?yōu)開Gwaitting,而M會(huì)跳過該G嘗試獲取并執(zhí)行下一個(gè)G,如果此時(shí)沒有可運(yùn)行的G供M運(yùn)行,那么M將解綁P,并進(jìn)入sleep狀態(tài);當(dāng)阻塞的G被另一端的G2喚醒時(shí)(比如channel的可讀/寫通知),G被標(biāo)記為,嘗試加入G2所在P的runnext(runnext是線程下一個(gè)需要執(zhí)行的 Goroutine。), 然后再是P的本地隊(duì)列和全局隊(duì)列。

系統(tǒng)調(diào)用阻塞

當(dāng)M執(zhí)行某一個(gè)G時(shí)候如果發(fā)生了阻塞操作,M會(huì)阻塞,如果當(dāng)前有一些G在執(zhí)行,調(diào)度器會(huì)把這個(gè)線程M從P中摘除,然后再創(chuàng)建一個(gè)新的操作系統(tǒng)的線程(如果有空閑的線程可用就復(fù)用空閑線程)來服務(wù)于這個(gè)P。當(dāng)M系統(tǒng)調(diào)用結(jié)束時(shí)候,這個(gè)G會(huì)嘗試獲取一個(gè)空閑的P執(zhí)行,并放入到這個(gè)P的本地隊(duì)列。如果獲取不到P,那么這個(gè)線程M變成休眠狀態(tài), 加入到空閑線程中,然后這個(gè)G會(huì)被放入全局隊(duì)列中。

隊(duì)列輪轉(zhuǎn)

可見每個(gè)P維護(hù)著一個(gè)包含G的隊(duì)列,不考慮G進(jìn)入系統(tǒng)調(diào)用或IO操作的情況下,P周期性的將G調(diào)度到M中執(zhí)行,執(zhí)行一小段時(shí)間,將上下文保存下來,然后將G放到隊(duì)列尾部,然后從隊(duì)列中重新取出一個(gè)G進(jìn)行調(diào)度。

除了每個(gè)P維護(hù)的G隊(duì)列以外,還有一個(gè)全局的隊(duì)列,每個(gè)P會(huì)周期性地查看全局隊(duì)列中是否有G待運(yùn)行并將其調(diào)度到M中執(zhí)行,全局隊(duì)列中G的來源,主要有從系統(tǒng)調(diào)用中恢復(fù)的G。之所以P會(huì)周期性地查看全局隊(duì)列,也是為了防止全局隊(duì)列中的G被餓死。

除了每個(gè)P維護(hù)的G隊(duì)列以外,還有一個(gè)全局的隊(duì)列,每個(gè)P會(huì)周期性地查看全局隊(duì)列中是否有G待運(yùn)行并將其調(diào)度到M中執(zhí)行,全局隊(duì)列中G的來源,主要有從系統(tǒng)調(diào)用中恢復(fù)的G。之所以P會(huì)周期性地查看全局隊(duì)列,也是為了防止全局隊(duì)列中的G被餓死。

M0

M0是啟動(dòng)程序后的編號(hào)為0的主線程,這個(gè)M對(duì)應(yīng)的實(shí)例會(huì)在全局變量rutime.m0中,不需要在heap上分配,M0負(fù)責(zé)執(zhí)行初始化操作和啟動(dòng)第一個(gè)G,在之后M0就和其他的M一樣了

G0

G0是每次啟動(dòng)一個(gè)M都會(huì)第一個(gè)創(chuàng)建的goroutine,G0僅用于負(fù)責(zé)調(diào)度G,G0不指向任何可執(zhí)行的函數(shù),每個(gè)M都會(huì)有一個(gè)自己的G0,在調(diào)度或系統(tǒng)調(diào)用時(shí)會(huì)使用G0的??臻g,全局變量的G0是M0的G0

一個(gè)G由于調(diào)度被中斷,此后如何恢復(fù)?

中斷的時(shí)候?qū)⒓拇嫫骼锏臈P畔ⅲ4娴阶约旱腉對(duì)象里面。當(dāng)再次輪到自己執(zhí)行時(shí),將自己保存的棧信息復(fù)制到寄存器里面,這樣就接著上次之后運(yùn)行了。

我這里只是根據(jù)自己的理解進(jìn)行了簡單的介紹,想要詳細(xì)了解有關(guān)GMP的底層原理可以去看Go調(diào)度器 G-P-M 模型的設(shè)計(jì)者的文檔或直接看源碼

參考: ()

()

golang sync.pool對(duì)象復(fù)用 并發(fā)原理 緩存池

在go http每一次go serve(l)都會(huì)構(gòu)建Request數(shù)據(jù)結(jié)構(gòu)。在大量數(shù)據(jù)請(qǐng)求或高并發(fā)的場景中,頻繁創(chuàng)建銷毀對(duì)象,會(huì)導(dǎo)致GC壓力。解決辦法之一就是使用對(duì)象復(fù)用技術(shù)。在http協(xié)議層之下,使用對(duì)象復(fù)用技術(shù)創(chuàng)建Request數(shù)據(jù)結(jié)構(gòu)。在http協(xié)議層之上,可以使用對(duì)象復(fù)用技術(shù)創(chuàng)建(w,*r,ctx)數(shù)據(jù)結(jié)構(gòu)。這樣即可以回快TCP層讀包之后的解析速度,也可也加快請(qǐng)求處理的速度。

先上一個(gè)測試:

結(jié)論是這樣的:

貌似使用池化,性能弱爆了???這似乎與net/http使用sync.pool池化Request來優(yōu)化性能的選擇相違背。這同時(shí)也說明了一個(gè)問題,好的東西,如果濫用反而造成了性能成倍的下降。在看過pool原理之后,結(jié)合實(shí)例,將給出正確的使用方法,并給出預(yù)期的效果。

sync.Pool是一個(gè) 協(xié)程安全 的 臨時(shí)對(duì)象池 。數(shù)據(jù)結(jié)構(gòu)如下:

local 成員的真實(shí)類型是一個(gè) poolLocal 數(shù)組,localSize 是數(shù)組長度。這涉及到Pool實(shí)現(xiàn),pool為每個(gè)P分配了一個(gè)對(duì)象,P數(shù)量設(shè)置為runtime.GOMAXPROCS(0)。在并發(fā)讀寫時(shí),goroutine綁定的P有對(duì)象,先用自己的,沒有去偷其它P的。go語言將數(shù)據(jù)分散在了各個(gè)真正運(yùn)行的P中,降低了鎖競爭,提高了并發(fā)能力。

不要習(xí)慣性地誤認(rèn)為New是一個(gè)關(guān)鍵字,這里的New是Pool的一個(gè)字段,也是一個(gè)閉包名稱。其API:

如果不指定New字段,對(duì)象池為空時(shí)會(huì)返回nil,而不是一個(gè)新構(gòu)建的對(duì)象。Get()到的對(duì)象是隨機(jī)的。

原生sync.Pool的問題是,Pool中的對(duì)象會(huì)被GC清理掉,這使得sync.Pool只適合做簡單地對(duì)象池,不適合作連接池。

pool創(chuàng)建時(shí)不能指定大小,沒有數(shù)量限制。pool中對(duì)象會(huì)被GC清掉,只存在于兩次GC之間。實(shí)現(xiàn)是pool的init方法注冊(cè)了一個(gè)poolCleanup()函數(shù),這個(gè)方法在GC之前執(zhí)行,清空pool中的所有緩存對(duì)象。

為使多協(xié)程使用同一個(gè)POOL。最基本的想法就是每個(gè)協(xié)程,加鎖去操作共享的POOL,這顯然是低效的。而進(jìn)一步改進(jìn),類似于ConcurrentHashMap(JDK7)的分Segment,提高其并發(fā)性可以一定程度性緩解。

注意到pool中的對(duì)象是無差異性的,加鎖或者分段加鎖都不是較好的做法。go的做法是為每一個(gè)綁定協(xié)程的P都分配一個(gè)子池。每個(gè)子池又分為私有池和共享列表。共享列表是分別存放在各個(gè)P之上的共享區(qū)域,而不是各個(gè)P共享的一塊內(nèi)存。協(xié)程拿自己P里的子池對(duì)象不需要加鎖,拿共享列表中的就需要加鎖了。

Get對(duì)象過程:

Put過程:

如何解決Get最壞情況遍歷所有P才獲取得對(duì)象呢:

方法1止前sync.pool并沒有這樣的設(shè)置。方法2由于goroutine被分配到哪個(gè)P由調(diào)度器調(diào)度不可控,無法確保其平衡。

由于不可控的GC導(dǎo)致生命周期過短,且池大小不可控,因而不適合作連接池。僅適用于增加對(duì)象重用機(jī)率,減少GC負(fù)擔(dān)。2

執(zhí)行結(jié)果:

單線程情況下,遍歷其它無元素的P,長時(shí)間加鎖性能低下。啟用協(xié)程改善。

結(jié)果:

測試場景在goroutines遠(yuǎn)大于GOMAXPROCS情況下,與非池化性能差異巨大。

測試結(jié)果

可以看到同樣使用*sync.pool,較大池大小的命中率較高,性能遠(yuǎn)高于空池。

結(jié)論:pool在一定的使用條件下提高并發(fā)性能,條件1是協(xié)程數(shù)遠(yuǎn)大于GOMAXPROCS,條件2是池中對(duì)象遠(yuǎn)大于GOMAXPROCS。歸結(jié)成一個(gè)原因就是使對(duì)象在各個(gè)P中均勻分布。

池pool和緩存cache的區(qū)別。池的意思是,池內(nèi)對(duì)象是可以互換的,不關(guān)心具體值,甚至不需要區(qū)分是新建的還是從池中拿出的。緩存指的是KV映射,緩存里的值互不相同,清除機(jī)制更為復(fù)雜。緩存清除算法如LRU、LIRS緩存算法。

池空間回收的幾種方式。一些是GC前回收,一些是基于時(shí)鐘或弱引用回收。最終確定在GC時(shí)回收Pool內(nèi)對(duì)象,即不回避GC。用java的GC解釋弱引用。GC的四種引用:強(qiáng)引用、弱引用、軟引用、虛引用。虛引用即沒有引用,弱引用GC但有空間則保留,軟引用GC即清除。ThreadLocal的值為弱引用的例子。

regexp 包為了保證并發(fā)時(shí)使用同一個(gè)正則,而維護(hù)了一組狀態(tài)機(jī)。

fmt包做字串拼接,從sync.pool拿[]byte對(duì)象。避免頻繁構(gòu)建再GC效率高很多。

Go語言設(shè)計(jì)與實(shí)現(xiàn)(上)

基本設(shè)計(jì)思路:

類型轉(zhuǎn)換、類型斷言、動(dòng)態(tài)派發(fā)。iface,eface。

反射對(duì)象具有的方法:

編譯優(yōu)化:

內(nèi)部實(shí)現(xiàn):

實(shí)現(xiàn) Context 接口有以下幾個(gè)類型(空實(shí)現(xiàn)就忽略了):

互斥鎖的控制邏輯:

設(shè)計(jì)思路:

(以上為寫被讀阻塞,下面是讀被寫阻塞)

總結(jié),讀寫鎖的設(shè)計(jì)還是非常巧妙的:

設(shè)計(jì)思路:

WaitGroup 有三個(gè)暴露的函數(shù):

部件:

設(shè)計(jì)思路:

結(jié)構(gòu):

Once 只暴露了一個(gè)方法:

實(shí)現(xiàn):

三個(gè)關(guān)鍵點(diǎn):

細(xì)節(jié):

讓多協(xié)程任務(wù)的開始執(zhí)行時(shí)間可控(按順序或歸一)。(Context 是控制結(jié)束時(shí)間)

設(shè)計(jì)思路: 通過一個(gè)鎖和內(nèi)置的 notifyList 隊(duì)列實(shí)現(xiàn),Wait() 會(huì)生成票據(jù),并將等待協(xié)程信息加入鏈表中,等待控制協(xié)程中發(fā)送信號(hào)通知一個(gè)(Signal())或所有(Boardcast())等待者(內(nèi)部實(shí)現(xiàn)是通過票據(jù)通知的)來控制協(xié)程解除阻塞。

暴露四個(gè)函數(shù):

實(shí)現(xiàn)細(xì)節(jié):

部件:

包: golang.org/x/sync/errgroup

作用:開啟 func() error 函數(shù)簽名的協(xié)程,在同 Group 下協(xié)程并發(fā)執(zhí)行過程并收集首次 err 錯(cuò)誤。通過 Context 的傳入,還可以控制在首次 err 出現(xiàn)時(shí)就終止組內(nèi)各協(xié)程。

設(shè)計(jì)思路:

結(jié)構(gòu):

暴露的方法:

實(shí)現(xiàn)細(xì)節(jié):

注意問題:

包: "golang.org/x/sync/semaphore"

作用:排隊(duì)借資源(如錢,有借有還)的一種場景。此包相當(dāng)于對(duì)底層信號(hào)量的一種暴露。

設(shè)計(jì)思路:有一定數(shù)量的資源 Weight,每一個(gè) waiter 攜帶一個(gè) channel 和要借的數(shù)量 n。通過隊(duì)列排隊(duì)執(zhí)行借貸。

結(jié)構(gòu):

暴露方法:

細(xì)節(jié):

部件:

細(xì)節(jié):

包: "golang.org/x/sync/singleflight"

作用:防擊穿。瞬時(shí)的相同請(qǐng)求只調(diào)用一次,response 被所有相同請(qǐng)求共享。

設(shè)計(jì)思路:按請(qǐng)求的 key 分組(一個(gè) *call 是一個(gè)組,用 map 映射存儲(chǔ)組),每個(gè)組只進(jìn)行一次訪問,組內(nèi)每個(gè)協(xié)程會(huì)獲得對(duì)應(yīng)結(jié)果的一個(gè)拷貝。

結(jié)構(gòu):

邏輯:

細(xì)節(jié):

部件:

如有錯(cuò)誤,請(qǐng)批評(píng)指正。

協(xié)程與異步IO

協(xié)程,又稱微線程,纖程。英文名 Coroutine 。Python對(duì)協(xié)程的支持是通過 generator 實(shí)現(xiàn)的。在generator中,我們不但可以通過for循環(huán)來迭代,還可以不斷調(diào)用 next()函數(shù) 獲取由 yield 語句返回的下一個(gè)值。但是Python的yield不但可以返回一個(gè)值,它還可以接收調(diào)用者發(fā)出的參數(shù)。yield其實(shí)是終端當(dāng)前的函數(shù),返回給調(diào)用方。python3中使用yield來實(shí)現(xiàn)range,節(jié)省內(nèi)存,提高性能,懶加載的模式。

asyncio是Python 3.4 版本引入的 標(biāo)準(zhǔn)庫 ,直接內(nèi)置了對(duì)異步IO的支持。

從Python 3.5 開始引入了新的語法 async 和 await ,用來簡化yield的語法:

import asyncio

import threading

async def compute(x, y):

print("Compute %s + %s ..." % (x, y))

print(threading.current_thread().name)

await asyncio.sleep(x + y)

return x + y

async def print_sum(x, y):

result = await compute(x, y)

print("%s + %s = %s" % (x, y, result))

print(threading.current_thread().name)

if __name__ == "__main__":

loop = asyncio.get_event_loop()

tasks = [print_sum(1, 2), print_sum(3, 4)]

loop.run_until_complete(asyncio.wait(tasks))

loop.close()

線程是內(nèi)核進(jìn)行搶占式的調(diào)度的,這樣就確保了每個(gè)線程都有執(zhí)行的機(jī)會(huì)。而 coroutine 運(yùn)行在同一個(gè)線程中,由語言的運(yùn)行時(shí)中的 EventLoop(事件循環(huán)) 來進(jìn)行調(diào)度。和大多數(shù)語言一樣,在 Python 中,協(xié)程的調(diào)度是非搶占式的,也就是說一個(gè)協(xié)程必須主動(dòng)讓出執(zhí)行機(jī)會(huì),其他協(xié)程才有機(jī)會(huì)運(yùn)行。

讓出執(zhí)行的關(guān)鍵字就是 await。也就是說一個(gè)協(xié)程如果阻塞了,持續(xù)不讓出 CPU,那么整個(gè)線程就卡住了,沒有任何并發(fā)。

PS: 作為服務(wù)端,event loop最核心的就是IO多路復(fù)用技術(shù),所有來自客戶端的請(qǐng)求都由IO多路復(fù)用函數(shù)來處理;作為客戶端,event loop的核心在于利用Future對(duì)象延遲執(zhí)行,并使用send函數(shù)激發(fā)協(xié)程,掛起,等待服務(wù)端處理完成返回后再調(diào)用CallBack函數(shù)繼續(xù)下面的流程

Go語言的協(xié)程是 語言本身特性 ,erlang和golang都是采用了CSP(Communicating Sequential Processes)模式(Python中的協(xié)程是eventloop模型),但是erlang是基于進(jìn)程的消息通信,go是基于goroutine和channel的通信。

Python和Go都引入了消息調(diào)度系統(tǒng)模型,來避免鎖的影響和進(jìn)程/線程開銷大的問題。

協(xié)程從本質(zhì)上來說是一種用戶態(tài)的線程,不需要系統(tǒng)來執(zhí)行搶占式調(diào)度,而是在語言層面實(shí)現(xiàn)線程的調(diào)度 。因?yàn)閰f(xié)程 不再使用共享內(nèi)存/數(shù)據(jù) ,而是使用 通信 來共享內(nèi)存/鎖,因?yàn)樵谝粋€(gè)超級(jí)大系統(tǒng)里具有無數(shù)的鎖,共享變量等等會(huì)使得整個(gè)系統(tǒng)變得無比的臃腫,而通過消息機(jī)制來交流,可以使得每個(gè)并發(fā)的單元都成為一個(gè)獨(dú)立的個(gè)體,擁有自己的變量,單元之間變量并不共享,對(duì)于單元的輸入輸出只有消息。開發(fā)者只需要關(guān)心在一個(gè)并發(fā)單元的輸入與輸出的影響,而不需要再考慮類似于修改共享內(nèi)存/數(shù)據(jù)對(duì)其它程序的影響。

Golang 線程和協(xié)程的區(qū)別

線程:

多線程是為了解決CPU利用率的問題,線程則是為了減少上下文切換時(shí)的開銷,進(jìn)程和線程在Linux中沒有本質(zhì)區(qū)別,最大的不同就是進(jìn)程有自己獨(dú)立的內(nèi)存空間,而線程是共享內(nèi)存空間。

在進(jìn)程切換時(shí)需要轉(zhuǎn)換內(nèi)存地址空間,而線程切換沒有這個(gè)動(dòng)作,所以線程切換比進(jìn)程切換代價(jià)要小得多。

協(xié)程:

想要簡單,又要性能高,協(xié)程就可以達(dá)到我們的目的,它是用戶視角的一種抽象,操作系統(tǒng)并沒有這個(gè)概念,主要思想是在用戶態(tài)實(shí)現(xiàn)調(diào)度算法,用少量線程完成大量任務(wù)的調(diào)度。

Goroutine是GO語言實(shí)現(xiàn)的協(xié)程,其特點(diǎn)是在語言層面就支持,使用起來十分方便,它的核心是MPG調(diào)度模型:M即內(nèi)核線程;P即處理器,用來執(zhí)行Goroutine,它維護(hù)了本地可運(yùn)行隊(duì)列;G即Goroutine,代碼和數(shù)據(jù)結(jié)構(gòu);S及調(diào)度器,維護(hù)M和P的信息。


分享題目:go語言開啟協(xié)程關(guān)鍵字,go 協(xié)程通信
鏈接地址:http://weahome.cn/article/dsgjshj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部