不同于傳統(tǒng)的多線程并發(fā)模型使用共享內(nèi)存來實(shí)現(xiàn)線程間通信的方式,golang 的哲學(xué)是通過 channel 進(jìn)行協(xié)程(goroutine)之間的通信來實(shí)現(xiàn)數(shù)據(jù)共享。這種方式的優(yōu)點(diǎn)是通過提供原子的通信原語,避免了競態(tài)情形(race condition)下復(fù)雜的鎖機(jī)制。
創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計(jì)制作、做網(wǎng)站、網(wǎng)頁設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)寧波,10余年網(wǎng)站建設(shè)經(jīng)驗(yàn),價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108
channel 可以看成一個 FIFO 隊(duì)列,對 FIFO 隊(duì)列的讀寫都是原子的操作,不需要加鎖。對 channel 的操作行為結(jié)果總結(jié)如下:
讀取一個已關(guān)閉的 channel 時,總是能讀取到對應(yīng)類型的零值,為了和讀取非空未關(guān)閉 channel 的行為區(qū)別,可以使用兩個接收值:
golang 中大部分類型都是值類型(只有 slice / channel / map 是引用類型),讀/寫類型是值類型的 channel 時, 如果元素 size 比較大時,應(yīng)該使用指針代替,避免頻繁的內(nèi)存拷貝開銷 。
main方法里創(chuàng)建了一個string類型的Channel,實(shí)現(xiàn)主協(xié)程與子協(xié)程 go SendMessage 進(jìn)行通信。主協(xié)程執(zhí)行到 -values 時發(fā)生阻塞,等待讀取 values Channel的值,而子協(xié)程執(zhí)行 SendMessage 方法時寫入 values Channel。即,主協(xié)程發(fā)生阻塞,等待子協(xié)程執(zhí)行完畢后再繼續(xù)執(zhí)行。
執(zhí)行的結(jié)果如下:
從日志可以看出,打印val語句必須在子協(xié)程 go SendMessage 執(zhí)行完成后才執(zhí)行。
如果在此基礎(chǔ)上添加多一個協(xié)程寫入 values Channel會發(fā)生什么?
在主協(xié)程中,啟動兩個子協(xié)程給Channel寫數(shù)據(jù)。而在 SendMessage 方法里,為了達(dá)到顯示效果,在寫入Channel前先睡眠1秒,在主協(xié)程也添加睡眠時間。
執(zhí)行日志打印如下:
發(fā)現(xiàn)只有其中一個協(xié)程完成寫入Channel的操作。因?yàn)榇薈hannel沒有設(shè)置緩存,所有只能保存一個寫入值。
那么如何才能保證兩個子協(xié)程能正常寫入Channel呢?
為了保證上面的兩個子協(xié)程能順利地把值寫入Channel,我們創(chuàng)建一個帶緩沖的Channel。
新創(chuàng)建的Channel緩沖兩個值,這樣就能保證兩個子協(xié)程能正常寫入到Channel中。下面看看打印日志:
如我們猜想一樣,兩個子協(xié)程都能順利結(jié)束。
晚安~
Go的CSP并發(fā)模型
Go實(shí)現(xiàn)了兩種并發(fā)形式。第一種是大家普遍認(rèn)知的:多線程共享內(nèi)存。其實(shí)就是Java或者C++等語言中的多線程開發(fā)。另外一種是Go語言特有的,也是Go語言推薦的:CSP(communicating sequential processes)并發(fā)模型。
CSP 是 Communicating Sequential Process 的簡稱,中文可以叫做通信順序進(jìn)程,是一種并發(fā)編程模型,由 Tony Hoare 于 1977 年提出。簡單來說,CSP 模型由并發(fā)執(zhí)行的實(shí)體(線程或者進(jìn)程)所組成,實(shí)體之間通過發(fā)送消息進(jìn)行通信,這里發(fā)送消息時使用的就是通道,或者叫 channel。CSP 模型的關(guān)鍵是關(guān)注 channel,而不關(guān)注發(fā)送消息的實(shí)體。 Go 語言實(shí)現(xiàn)了 CSP 部分理論 。
“ 不要以共享內(nèi)存的方式來通信,相反, 要通過通信來共享內(nèi)存。”
Go的CSP并發(fā)模型,是通過 goroutine和channel 來實(shí)現(xiàn)的。
goroutine 是Go語言中并發(fā)的執(zhí)行單位。其實(shí)就是協(xié)程。
channel是Go語言中各個并發(fā)結(jié)構(gòu)體(goroutine)之前的通信機(jī)制。 通俗的講,就是各個goroutine之間通信的”管道“,有點(diǎn)類似于Linux中的管道。
Channel
Goroutine
前段時間在golang-China讀到這個貼:
個人覺得golang十分適合進(jìn)行網(wǎng)游服務(wù)器端開發(fā),寫下這篇文章總結(jié)一下。
從網(wǎng)游的角度看:
要成功的運(yùn)營一款網(wǎng)游,很大程度上依賴于玩家自發(fā)形成的社區(qū)。只有玩家自發(fā)形成一個穩(wěn)定的生態(tài)系統(tǒng),游戲才能持續(xù)下去,避免鬼城的出現(xiàn)。而這就需要多次大量導(dǎo)入用戶,在同時在線用戶量達(dá)到某個臨界點(diǎn)的時候,才有可能完成。因此,多人同時在線十分有必要。
再來看網(wǎng)游的常見玩法,除了排行榜這類統(tǒng)計(jì)和數(shù)據(jù)匯總的功能外,基本沒有需要大量CPU時間的應(yīng)用。以前的項(xiàng)目里,即時戰(zhàn)斗產(chǎn)生的各種傷害計(jì)算對CPU的消耗也不大。玩家要完成一次操作,需要通過客戶端-服務(wù)器端-客戶端這樣一個來回,為了獲得高響應(yīng)速度,滿足玩家體驗(yàn),服務(wù)器端的處理也不能占用太多時間。所以,每次請求對應(yīng)的CPU占用是比較小的。
網(wǎng)游的IO主要分兩個方面,一個是網(wǎng)絡(luò)IO,一個是磁盤IO。網(wǎng)絡(luò)IO方面,可以分成美術(shù)資源的IO和游戲邏輯指令的IO,這里主要分析游戲邏輯的IO。游戲邏輯的IO跟CPU占用的情況相似,每次請求的字節(jié)數(shù)很小,但由于多人同時在線,因此并發(fā)數(shù)相當(dāng)高。另外,地圖信息的廣播也會帶來比較頻繁的網(wǎng)絡(luò)通信。磁盤IO方面,主要是游戲數(shù)據(jù)的保存。采用不同的數(shù)據(jù)庫,會有比較大的區(qū)別。以前的項(xiàng)目里,就經(jīng)歷了從MySQL轉(zhuǎn)向MongoDB這種內(nèi)存數(shù)據(jù)庫的過程,磁盤IO不再是瓶頸。總體來說,還是用內(nèi)存做一級緩沖,避免大量小數(shù)據(jù)塊讀寫的方案。
針對網(wǎng)游的這些特點(diǎn),golang的語言特性十分適合開發(fā)游戲服務(wù)器端。
首先,go語言提供goroutine機(jī)制作為原生的并發(fā)機(jī)制。每個goroutine所需的內(nèi)存很少,實(shí)際應(yīng)用中可以啟動大量的goroutine對并發(fā)連接進(jìn)行響應(yīng)。goroutine與gevent中的greenlet很相像,遇到IO阻塞的時候,調(diào)度器就會自動切換到另一個goroutine執(zhí)行,保證CPU不會因?yàn)镮O而發(fā)生等待。而goroutine與gevent相比,沒有了python底層的GIL限制,就不需要利用多進(jìn)程來榨取多核機(jī)器的性能了。通過設(shè)置最大線程數(shù),可以控制go所啟動的線程,每個線程執(zhí)行一個goroutine,讓CPU滿負(fù)載運(yùn)行。
同時,go語言為goroutine提供了獨(dú)到的通信機(jī)制channel。channel發(fā)生讀寫的時候,也會掛起當(dāng)前操作channel的goroutine,是一種同步阻塞通信。這樣既達(dá)到了通信的目的,又實(shí)現(xiàn)同步,用CSP模型的觀點(diǎn)看,并發(fā)模型就是通過一組進(jìn)程和進(jìn)程間的事件觸發(fā)解決任務(wù)的。雖然說,主流的編程語言之間,只要是圖靈完備的,他們就都能實(shí)現(xiàn)相同的功能。但go語言提供的這種協(xié)程間通信機(jī)制,十分優(yōu)雅地揭示了協(xié)程通信的本質(zhì),避免了以往鎖的顯式使用帶給程序員的心理負(fù)擔(dān),確是一大優(yōu)勢。進(jìn)行網(wǎng)游開發(fā)的程序員,可以將游戲邏輯按照單線程阻塞式的寫,不需要額外考慮線程調(diào)度的問題,以及線程間數(shù)據(jù)依賴的問題。因?yàn)?,線程間的channel通信,已經(jīng)表達(dá)了線程間的數(shù)據(jù)依賴關(guān)系了,而go的調(diào)度器會給予妥善的處理。
另外,go語言提供的gc機(jī)制,以及對指針的保護(hù)式使用,可以大大減輕程序員的開發(fā)壓力,提高開發(fā)效率。
展望未來,我期待go語言社區(qū)能夠提供更多的goroutine間的隔離機(jī)制。個人十分推崇erlang社區(qū)的脆崩哲學(xué),推動應(yīng)用發(fā)生預(yù)期外行為時,盡早崩潰,再fork出新進(jìn)程處理新的請求。對于協(xié)程機(jī)制,需要由程序員保證執(zhí)行的函數(shù)不會發(fā)生死循環(huán),導(dǎo)致線程卡死。如果能夠定制goroutine所執(zhí)行函數(shù)的最大CPU執(zhí)行時間,及所能使用的最大內(nèi)存空間,對于提升系統(tǒng)的魯棒性,大有裨益。
福哥答案2020-08-20:
1.golang的協(xié)程是基于gpm機(jī)制,是可以多核多線程的。Python的協(xié)程是eventloop模型(IO多路復(fù)用技術(shù))實(shí)現(xiàn),協(xié)程是嚴(yán)格的 1:N 關(guān)系,也就是一個線程對應(yīng)了多個協(xié)程。雖然可以實(shí)現(xiàn)異步I/O,但是不能有效利用多核(GIL)。
2.golang用go func。python用import asyncio,async/await表達(dá)式。
評論
【譯文】 原文地址
channel是Go語言的一個標(biāo)志性特性,為go協(xié)程之間的數(shù)據(jù)交互提供一種非常強(qiáng)大的方式,而不需要使用鎖機(jī)制。
本文將討論channel的兩個重要屬性,一個是控制協(xié)程間數(shù)據(jù)發(fā)送和接收,以及對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就會被賦值為false表示channel已經(jīng)關(guān)閉,而且value值為對應(yīng)channel類型bool的默認(rèn)零值false。只要不停地從關(guān)閉的channel接收,就會無限的返回默認(rèn)值和false??梢詫or循環(huán)次數(shù)改大點(diǎn)試試即可驗(yàn)證。
通過以上例子可以發(fā)現(xiàn),關(guān)閉的channel可以繼續(xù)接收讀取操作,這種特征是有用的。在使用range讀取帶緩存的channel時就會用到,一旦channel關(guān)閉,讀取完緩存中數(shù)據(jù)就會停止接收數(shù)據(jù)退出。
將前面的例子改為如下:
output:
上面的例子就沒有false打出來了。正好是寫入channel里面的兩個值。
channel與select結(jié)合更能發(fā)揮出其作用,讓我們看一個例子:
上面的例子,因?yàn)閒inish在主協(xié)程中發(fā)送之后,馬上就會在select中接收,并執(zhí)行done.Done()。主協(xié)程wait馬上會退出整個程序就結(jié)束。但是這里面存在一個問題,如果在select中沒有添加finish case的話,主協(xié)程就永遠(yuǎn)發(fā)送不了數(shù)據(jù)到finish這個channel,因?yàn)槠洳粠Ь彺妗_@里就可以通過將finish改成帶緩存的channel,或者可以讓select中的finish不會阻塞。
但是出現(xiàn)多個協(xié)程都在接收finish通道中的數(shù)據(jù)的話,就需要發(fā)送對應(yīng)協(xié)程數(shù)量的值到channel中才能解決上面的問題。但是具體有多少個協(xié)程這往往是不好確定的,因?yàn)橛行﹨f(xié)程可能是程序其他部分創(chuàng)建的。一個比較好的選擇就是通過使用關(guān)閉通道的方法來實(shí)現(xiàn)各協(xié)程能正常接收并結(jié)束。
如下所示:
output:
上面的例子就是使用了關(guān)閉的channel可以無限地接收到反饋數(shù)據(jù)。這樣每個協(xié)程都能從finish通道中讀到關(guān)閉信息并執(zhí)行done.Done()使得主協(xié)程wait能退出。并且不需要關(guān)注多少個協(xié)程數(shù),就能正確的讓所有協(xié)程讀到finish通道信息。
channel的這個特性,可以讓程序員無需關(guān)注后臺具體執(zhí)行協(xié)程個數(shù),確保每個協(xié)程都能接收到通道關(guān)閉信息,而無需擔(dān)心死鎖問題。
通過上面的例子我們也發(fā)現(xiàn)每個協(xié)程并不需要從通道中讀取對應(yīng)類型的數(shù)據(jù),只需讓接收操作能執(zhí)行就行,讓select不被阻塞。所以可以使用空結(jié)構(gòu)體類型,我們可以改成如下:
這里我們只關(guān)注通道是否關(guān)閉這個信號,而不需要關(guān)注通道里面的數(shù)據(jù),所以可使用空結(jié)構(gòu)體類型通道。
第二個要討論的是nil通道:如果定義了一個channel變量沒有被初始化,或者被賦值為nil,那么該chennel總是處于阻塞狀態(tài)。如下所示:
執(zhí)行結(jié)果為:
因?yàn)閏hannel為nil無法發(fā)送數(shù)據(jù),當(dāng)然也不能接收數(shù)據(jù):
這個似乎看起來不是很重要,但是如果你想使用關(guān)閉channel來等待多個channel關(guān)閉的話,這個特性就有用處了。先看下面的例子:
WaitMany()函數(shù)看起來好像是一個等待通道a和b關(guān)閉的好方法,但是存在一個問題。假設(shè)a通道先關(guān)閉,case -a就會變成非阻塞。因?yàn)閎closed還是false,程序就會進(jìn)入到一個死循環(huán)當(dāng)中,導(dǎo)致b通道永遠(yuǎn)無法確認(rèn)關(guān)閉。
一個安全的方法就是使用nil通道總是阻塞的特點(diǎn),如下所示:
上面的例子我們在WaitMany函數(shù)當(dāng)中,當(dāng)a或者b關(guān)閉時,case可執(zhí)行了將對應(yīng)的通道賦值為nil,讓其阻塞這樣就可以等待另一個通道關(guān)閉。當(dāng)nil通道是select語句的一部分時,它會被有效地忽略,因此nil通道a會從select中刪除它,只留下b,直到它被關(guān)閉,退出循環(huán)。
總之,closed和nil通道的簡單屬性對寫出優(yōu)質(zhì)的go程序是很有用的,可以用來創(chuàng)建高并發(fā)程序。
協(xié)程,又稱微線程,纖程。英文名 Coroutine 。Python對協(xié)程的支持是通過 generator 實(shí)現(xiàn)的。在generator中,我們不但可以通過for循環(huán)來迭代,還可以不斷調(diào)用 next()函數(shù) 獲取由 yield 語句返回的下一個值。但是Python的yield不但可以返回一個值,它還可以接收調(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)置了對異步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)度的,這樣就確保了每個線程都有執(zhí)行的機(jī)會。而 coroutine 運(yùn)行在同一個線程中,由語言的運(yùn)行時中的 EventLoop(事件循環(huán)) 來進(jìn)行調(diào)度。和大多數(shù)語言一樣,在 Python 中,協(xié)程的調(diào)度是非搶占式的,也就是說一個協(xié)程必須主動讓出執(zhí)行機(jī)會,其他協(xié)程才有機(jī)會運(yùn)行。
讓出執(zhí)行的關(guān)鍵字就是 await。也就是說一個協(xié)程如果阻塞了,持續(xù)不讓出 CPU,那么整個線程就卡住了,沒有任何并發(fā)。
PS: 作為服務(wù)端,event loop最核心的就是IO多路復(fù)用技術(shù),所有來自客戶端的請求都由IO多路復(fù)用函數(shù)來處理;作為客戶端,event loop的核心在于利用Future對象延遲執(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)樵谝粋€超級大系統(tǒng)里具有無數(shù)的鎖,共享變量等等會使得整個系統(tǒng)變得無比的臃腫,而通過消息機(jī)制來交流,可以使得每個并發(fā)的單元都成為一個獨(dú)立的個體,擁有自己的變量,單元之間變量并不共享,對于單元的輸入輸出只有消息。開發(fā)者只需要關(guān)心在一個并發(fā)單元的輸入與輸出的影響,而不需要再考慮類似于修改共享內(nèi)存/數(shù)據(jù)對其它程序的影響。
新聞標(biāo)題:GO語言協(xié)程間通信,go 協(xié)程調(diào)度
分享地址:http://sd-ha.com/article32/hcoesc.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)、靜態(tài)網(wǎng)站、品牌網(wǎng)站設(shè)計(jì)、網(wǎng)頁設(shè)計(jì)公司、網(wǎng)站設(shè)計(jì)公司、微信小程序
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)