Go語(yǔ)言中并發(fā)的工作原理
一、Go語(yǔ)言中Goroutine的基本原理
Go語(yǔ)言里的并發(fā)指的是能讓某個(gè)函數(shù)獨(dú)立于其他函數(shù)運(yùn)行的能力。
Go語(yǔ)言的goroutine是一個(gè)獨(dú)立的工作單元,
Go 語(yǔ)言的并發(fā)同步模型來(lái)自一個(gè)叫作通信順序進(jìn)程(Communicating Sequential Processes,CSP)的范型(paradigm)。
CSP 是一種消息傳遞模型,通過(guò)在goroutine 之間傳遞數(shù)據(jù)來(lái)傳遞消息,而不是對(duì)數(shù)據(jù)進(jìn)行加鎖來(lái)實(shí)現(xiàn)同步訪問(wèn)。消息的傳遞通過(guò)Go語(yǔ)言中的Channel(通道)來(lái)實(shí)現(xiàn)。
進(jìn)程(process) 看作一個(gè)包含了應(yīng)用程序在運(yùn)行中需要用到和維護(hù)的各種資源的容器。
線程(Thread)是一個(gè)執(zhí)行空間,這個(gè)空間會(huì)被操作系統(tǒng)調(diào)度來(lái)運(yùn)行函數(shù)中所寫的代碼。
每個(gè)進(jìn)程至少包含一個(gè)線程,每個(gè)進(jìn)程的初始線程被稱作主線程。因?yàn)閳?zhí)行這個(gè)線程的空間是應(yīng)用程序的本身的空間,所以當(dāng)主線程終止時(shí),應(yīng)用程序也會(huì)終止。
下圖就是進(jìn)程和線程的簡(jiǎn)要關(guān)系。
補(bǔ)充:句柄是什么?
我的理解句柄就是給用戶操作內(nèi)核資源的指針,指向指針的指針?!景咽帧亢汀鹃T】的關(guān)系,使用們把手能轉(zhuǎn)動(dòng)整個(gè)門(系統(tǒng)資源)
操作系統(tǒng)會(huì)在物理處理器上調(diào)度線程來(lái)運(yùn)行,而Go 語(yǔ)言的運(yùn)行時(shí)會(huì)在邏輯處理器上調(diào)度goroutine來(lái)運(yùn)行。每個(gè)邏輯處理器都分別綁定到單個(gè)操作系統(tǒng)線程。下面我們就來(lái)簡(jiǎn)單描述一下goroutine執(zhí)行的流程。
首先來(lái)了解一下操作系統(tǒng)線程、邏輯處理器和本地運(yùn)行隊(duì)列
全局運(yùn)行隊(duì)列:剛創(chuàng)建的goruntine會(huì)被安排到這里面,通過(guò)一些調(diào)度算法,分配給邏輯處理器
操作系統(tǒng)線程:這個(gè)就是傳統(tǒng)意義上的線程,用于具體goroutine的執(zhí)行,他會(huì)和一個(gè)邏輯器進(jìn)行綁定
邏輯處理器:里面維護(hù)著一個(gè)本地的隊(duì)列,用于本地隊(duì)列里面goroutine的調(diào)度
本地運(yùn)行隊(duì)列:裝載著待執(zhí)行的goruntine
支撐整個(gè)調(diào)度器的主要有4個(gè)重要結(jié)構(gòu),分別是M、G、P、Sched,前三個(gè)定義在runtime.h中,Sched定義在proc.c中。
- Sched結(jié)構(gòu)就是調(diào)度器,它維護(hù)有存儲(chǔ)M和G的隊(duì)列以及調(diào)度器的一些狀態(tài)信息等。
- M代表內(nèi)核級(jí)線程,一個(gè)M就是一個(gè)線程,goroutine就是跑在M之上的;M是一個(gè)很大的結(jié)構(gòu),里面維護(hù)小對(duì)象內(nèi)存cache(mcache)、當(dāng)前執(zhí)行的goroutine、隨機(jī)數(shù)發(fā)生器等等非常多的信息。
- P全稱是Processor,處理器,它的主要用途就是用來(lái)執(zhí)行g(shù)oroutine的,所以它也維護(hù)了一個(gè)goroutine隊(duì)列,里面存儲(chǔ)了所有需要它來(lái)執(zhí)行的goroutine,這個(gè)P的角色可能有一點(diǎn)讓人迷惑,一開始容易和M沖突,后面重點(diǎn)聊一下它們的關(guān)系。
- G就是goroutine實(shí)現(xiàn)的核心結(jié)構(gòu)了,G維護(hù)了goroutine需要的棧、程序計(jì)數(shù)器以及它所在的M等信息。
整個(gè)過(guò)程描述:
當(dāng)創(chuàng)建一個(gè)Goroutine的時(shí)候,先放到全局隊(duì)列當(dāng)中,然后會(huì)把這個(gè)Goroutine分配到一個(gè)邏輯處理器的本地隊(duì)列中,這個(gè)邏輯處理器會(huì)綁定一個(gè)操作系統(tǒng)線程,由這個(gè)線程去執(zhí)行Goroutine的代碼。
生動(dòng)描述:
地鼠(gopher)用小車運(yùn)著一堆待加工的磚。M就可以看作圖中的地鼠,P就是小車,G就是小車?yán)镅b的磚。一圖勝千言啊,弄清楚了它們?nèi)叩年P(guān)系,下面我們就開始重點(diǎn)聊地鼠是如何在搬運(yùn)磚塊的。
1.runqget, 地鼠(M)試圖從自己的小車(P)取出一塊磚(G),當(dāng)然結(jié)果可能失敗,也就是這個(gè)地鼠的小車已經(jīng)空了,沒有磚了。
2.findrunnable, 如果地鼠自己的小車中沒有磚,那也不能閑著不干活是吧,所以地鼠就會(huì)試圖跑去工場(chǎng)倉(cāng)庫(kù)取一塊磚來(lái)處理;工場(chǎng)倉(cāng)庫(kù)也可能沒磚啊,出現(xiàn)這種情況的時(shí)候,這個(gè)地鼠也沒有偷懶停下干活,而是悄悄跑出去,隨機(jī)盯上一個(gè)小伙伴(地鼠),然后從它的車?yán)镌噲D偷一半磚到自己車?yán)铩H绻啻螄L試偷磚都失敗了,那說(shuō)明實(shí)在沒有磚可搬了,這個(gè)時(shí)候地鼠就會(huì)把小車還回停車場(chǎng),然后睡覺休息了。如果地鼠睡覺了,下面的過(guò)程當(dāng)然都停止了,地鼠睡覺也就是線程sleep了。
3.wakep, 到這個(gè)過(guò)程的時(shí)候,可憐的地鼠發(fā)現(xiàn)自己小車?yán)镉泻枚啻u啊,自己根本處理不過(guò)來(lái);再回頭一看停車場(chǎng)居然有閑置的小車,立馬跑到宿舍一看,你妹,居然還有小伙伴在睡覺,直接給屁股一腳,“你妹,居然還在睡覺,老子都快累死了,趕緊起來(lái)干活,分擔(dān)點(diǎn)工作。”,小伙伴醒了,拿上自己的小車,乖乖干活去了。有時(shí)候,可憐的地鼠跑到宿舍卻發(fā)現(xiàn)沒有在睡覺的小伙伴,于是會(huì)很失望,最后只好向工場(chǎng)老板說(shuō)——”停車場(chǎng)還有閑置的車啊,我快干不動(dòng)了,趕緊從別的工場(chǎng)借個(gè)地鼠來(lái)幫忙吧。”,最后工場(chǎng)老板就搞來(lái)一個(gè)新的地鼠干活了。
4.execute,地鼠拿著磚放入火種歡快的燒練起來(lái)。
注: “地鼠偷磚”叫work stealing,一種調(diào)度算法。
到這里,貌似整個(gè)工場(chǎng)都正常的運(yùn)轉(zhuǎn)起來(lái)了,無(wú)懈可擊的樣子。不對(duì),還有一個(gè)疑點(diǎn)沒解決啊,假設(shè)地鼠的車?yán)镉泻芏啻u,它把一塊磚放入火爐中后,何時(shí)把它取出來(lái),放入第二塊磚呢?難道要一直把第一塊磚燒練好,才取出來(lái)嗎?那估計(jì)后面的磚真的是等得花兒都要謝了。這里就是要真正解決goroutine的調(diào)度,上下文切換問(wèn)題。
goroutine的阻塞
當(dāng)一個(gè)OS線程M0陷入阻塞時(shí)(如下圖),P轉(zhuǎn)而在運(yùn)行M1,圖中的M1可能是正被創(chuàng)建,或者從線程緩存中取出。
簡(jiǎn)單解釋就是,當(dāng)當(dāng)前的Goroutine在阻塞,就把他和當(dāng)前線程綁定,讓當(dāng)前的線程繼續(xù)執(zhí)行這個(gè)Goroutine(就是等待),其他的goroutine隨著邏輯處理器被綁定到另一個(gè)線程上,繼續(xù)執(zhí)行。
當(dāng)阻塞的線程返回時(shí),它必須嘗試取得一個(gè)邏輯處理器來(lái)運(yùn)行g(shù)oroutine,一般情況下,它會(huì)從其他的OS線程那里拿一個(gè)P過(guò)來(lái),如果沒有拿到的話,它就把goroutine放在一個(gè)全局運(yùn)行隊(duì)列里,然后自己睡眠(放入線程緩存里)。所有的P也會(huì)周期性的檢查global runqueue并運(yùn)行其中的goroutine,否則global runqueue上的goroutine永遠(yuǎn)無(wú)法執(zhí)行。
還有就像下面的圖片,左邊的線程一個(gè)滿載狀態(tài),一個(gè)沒有g(shù)oroutine,此時(shí)的做法就是會(huì)進(jìn)行重新分配,就想右側(cè)圖展示
參考 :https://morsmachine.dk/go-scheduler
二、Go語(yǔ)言中的并發(fā)和并行
并發(fā)是兩個(gè)任務(wù)可以在重疊的時(shí)間段內(nèi)啟動(dòng),運(yùn)行和完成。并行是任務(wù)在同一時(shí)間運(yùn)行,例如,在多核處理器上。
并發(fā)是獨(dú)立執(zhí)行過(guò)程的組合,而并行是同時(shí)執(zhí)行(可能相關(guān)的)計(jì)算。
并發(fā)是一次處理很多事情,并行是同時(shí)做很多事情。
- 應(yīng)用程序可以是并發(fā)的,但不是并行的,這意味著它可以同時(shí)處理多個(gè)任務(wù),但是沒有兩個(gè)任務(wù)在同一時(shí)刻執(zhí)行。
- 應(yīng)用程序可以是并行的,但不是并發(fā)的,這意味著它同時(shí)處理多核CPU中的任務(wù)的多個(gè)子任務(wù)。
- 應(yīng)用程序可以即不是并行的,也不是并發(fā)的,這意味著它一次一個(gè)地處理所有任務(wù)。
- 應(yīng)用程序可以即是并行的也是并發(fā)的,這意味著它同時(shí)在多核CPU中同時(shí)處理多個(gè)任務(wù)。
簡(jiǎn)單講:并行就是同時(shí)做很多事 并發(fā)就是一堆事情一個(gè)時(shí)間點(diǎn)來(lái)了,然后通過(guò)分片等方式感覺像都在執(zhí)行
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)腳本之家的支持。如果你想了解更多相關(guān)內(nèi)容請(qǐng)查看下面相關(guān)鏈接
相關(guān)文章
詳解Go語(yǔ)言中Get/Post請(qǐng)求測(cè)試
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言中的環(huán)境安裝以及Get和Post請(qǐng)求接口的測(cè)試,文中的示例代碼講解詳細(xì),感興趣的可以跟隨小編一起學(xué)習(xí)一下2022-06-06一文帶你熟悉Go語(yǔ)言中的分支結(jié)構(gòu)
這篇文章主要和大家分享一下Go語(yǔ)言中的分支結(jié)構(gòu)(if?-?else-if?-?else、switch),文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Go語(yǔ)言有一定的幫助,需要的可以參考一下2022-11-11Go語(yǔ)言中使用flag包對(duì)命令行進(jìn)行參數(shù)解析的方法
這篇文章主要介紹了Go語(yǔ)言中使用flag包對(duì)命令行進(jìn)行參數(shù)解析的方法,文中舉了一個(gè)實(shí)現(xiàn)flag.Value接口來(lái)自定義flag的例子,需要的朋友可以參考下2016-04-04一文帶你了解Golang中類型轉(zhuǎn)換庫(kù)cast的使用
你是否在使用 Go 的過(guò)程中因?yàn)轭愋娃D(zhuǎn)換的繁瑣而苦惱過(guò)?你是否覺得 Go 語(yǔ)言中的類型斷言可能會(huì) panic 而對(duì)自己寫的代碼有那么一點(diǎn)點(diǎn)不放心?本文就為大家推薦一個(gè)用于類型轉(zhuǎn)換的第三方庫(kù) cast 絕對(duì)是一個(gè)值得嘗試的選擇2023-02-02Go語(yǔ)言基礎(chǔ)知識(shí)總結(jié)(語(yǔ)法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等)
這篇文章主要介紹了Go語(yǔ)言基礎(chǔ)知識(shí)總結(jié)(語(yǔ)法、變量、數(shù)值類型、表達(dá)式、控制結(jié)構(gòu)等),本文匯總了Go語(yǔ)言的入門知識(shí),需要的朋友可以參考下2014-10-10