Golang goroutine

1_CdjOgfolLt_GNJYBzI-1QQ.jpeg

goroutine簡(jiǎn)介

goroutine是go語(yǔ)言中最為NB的設(shè)計(jì)芥吟,也是其魅力所在侦铜,goroutine的本質(zhì)是協(xié)程专甩,是實(shí)現(xiàn)并行計(jì)算的核心。goroutine使用方式非常的簡(jiǎn)單钉稍,只需使用go關(guān)鍵字即可啟動(dòng)一個(gè)協(xié)程涤躲,并且它是處于異步方式運(yùn)行,你不需要等它運(yùn)行完成以后在執(zhí)行以后的代碼贡未。GO默認(rèn)是使用一個(gè)CPU核的种樱,除非設(shè)置runtime.GOMAXPROCS。

go func()//通過(guò)go關(guān)鍵字啟動(dòng)一個(gè)協(xié)程來(lái)運(yùn)行函數(shù)   

概念普及

  • 并發(fā)
    一個(gè)cpu上能同時(shí)執(zhí)行多項(xiàng)任務(wù)俊卤,在很短時(shí)間內(nèi)嫩挤,cpu來(lái)回切換任務(wù)執(zhí)行(在某段很短時(shí)間內(nèi)執(zhí)行程序a,然后又迅速得切換到程序b去執(zhí)行)消恍,有時(shí)間上的重疊(宏觀上是同時(shí)的岂昭,微觀仍是順序執(zhí)行),這樣看起來(lái)多個(gè)任務(wù)像是同時(shí)執(zhí)行,這就是并發(fā)哺哼。
  • 并行
    當(dāng)系統(tǒng)有多個(gè)CPU(多核)時(shí),每個(gè)CPU同一時(shí)刻都運(yùn)行任務(wù)佩抹,互不搶占自己所在的CPU資源,同時(shí)進(jìn)行取董,稱為并行棍苹。
  • 進(jìn)程
    cpu在切換程序的時(shí)候,如果不保存上一個(gè)程序的狀態(tài)(也就是我們常說(shuō)的context--上下文)茵汰,直接切換下一個(gè)程序枢里,就會(huì)丟失上一個(gè)程序的一系列狀態(tài),于是引入了進(jìn)程這個(gè)概念蹂午,用以劃分好程序運(yùn)行時(shí)所需要的資源栏豺。因此進(jìn)程就是一個(gè)程序運(yùn)行時(shí)候的所需要的基本資源單位(也可以說(shuō)是程序運(yùn)行的一個(gè)實(shí)體)。(系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位)
  • 線程
    cpu切換多個(gè)進(jìn)程的時(shí)候豆胸,會(huì)花費(fèi)不少的時(shí)間奥洼,因?yàn)榍袚Q進(jìn)程需要切換到內(nèi)核態(tài),而每次調(diào)度需要內(nèi)核態(tài)都需要讀取用戶態(tài)的數(shù)據(jù)晚胡,進(jìn)程一旦多起來(lái)灵奖,cpu調(diào)度會(huì)消耗一大堆資源,因此引入了線程的概念估盘,線程本身幾乎不占有資源瓷患,他們共享進(jìn)程里的資源,內(nèi)核調(diào)度起來(lái)不會(huì)那么像進(jìn)程切換那么耗費(fèi)資源遣妥。
  • 協(xié)程
    協(xié)程擁有自己的寄存器上下文和棧擅编。協(xié)程調(diào)度切換時(shí),將寄存器上下文和棧保存到其他地方,在切回來(lái)的時(shí)候爱态,恢復(fù)先前保存的寄存器上下文和棧谭贪。因此,協(xié)程能保留上一次調(diào)用時(shí)的狀態(tài)(即所有局部狀態(tài)的一個(gè)特定組合)肢藐,每次過(guò)程重入時(shí)故河,就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài),換種說(shuō)法:進(jìn)入上一次離開時(shí)所處邏輯流的位置吆豹。線程和進(jìn)程的操作是由程序觸發(fā)系統(tǒng)接口鱼的,最后的執(zhí)行者是系統(tǒng);協(xié)程的操作執(zhí)行者則是用戶自身程序痘煤,goroutine也是協(xié)程凑阶。協(xié)程位于線程級(jí)別

調(diào)度模型簡(jiǎn)介

groutine能擁有強(qiáng)大的并發(fā)實(shí)現(xiàn)是通過(guò)GPM調(diào)度模型實(shí)現(xiàn)。
Go的調(diào)度器內(nèi)部有四個(gè)重要的結(jié)構(gòu):M衷快,P宙橱,G,Sched

  • 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ā)生器等等非常多的信息邓萨。
  • G :代表一個(gè)goroutine地梨,它有自己的棧,instruction pointer和其他信息(正在等待的channel等等)缔恳,用于調(diào)度宝剖。
  • P :全稱是Processor,處理器歉甚,它的主要用途就是用來(lái)執(zhí)行g(shù)oroutine的万细,所以它也維護(hù)了一個(gè)goroutine隊(duì)列,里面存儲(chǔ)了所有需要它來(lái)執(zhí)行的goroutine纸泄。
  • Sched :代表調(diào)度器赖钞,它維護(hù)有存儲(chǔ)M和G的隊(duì)列以及調(diào)度器的一些狀態(tài)信息等。

調(diào)度實(shí)現(xiàn)場(chǎng)景

一般調(diào)度如下圖所示:

goroutine.png

從上圖中看刃滓,有2個(gè)物理線程M1和M2,每一個(gè)M都擁有一個(gè)處理器P耸弄,每一個(gè)也都有一個(gè)正在運(yùn)行的goroutine咧虎。
P的數(shù)量可以通過(guò)GOMAXPROCS()來(lái)設(shè)置,它其實(shí)也就代表了真正的并發(fā)度计呈,即有多少個(gè)goroutine可以同時(shí)運(yùn)行砰诵。
圖中灰色的那些goroutine并沒有運(yùn)行征唬,而是出于ready的就緒態(tài),正在等待被調(diào)度茁彭。P維護(hù)著這個(gè)隊(duì)列(稱之為runqueue)总寒,
Go語(yǔ)言里,啟動(dòng)一個(gè)goroutine很容易:go function 就行理肺,所以每有一個(gè)go語(yǔ)句被執(zhí)行摄闸,runqueue隊(duì)列就在其末尾加入一個(gè)
goroutine,在下一個(gè)調(diào)度點(diǎn)妹萨,就從runqueue中取出(如何決定取哪個(gè)goroutine年枕?)一個(gè)goroutine執(zhí)行。

當(dāng)一個(gè)OS線程M1陷入阻塞時(shí):

P轉(zhuǎn)而在運(yùn)行M2乎完,圖中的M2可能是正被創(chuàng)建熏兄,或者從線程緩存中取出。當(dāng)M1返回時(shí)树姨,它必須嘗試取得一個(gè)P來(lái)運(yùn)行g(shù)oroutine摩桶,一般情況下,它會(huì)從其他的OS線程那里拿一個(gè)P過(guò)來(lái)帽揪,

如果沒有拿到的話硝清,它就把goroutine放在一個(gè)global runqueue里,然后自己睡眠(放入線程緩存里)台丛。所有的P也會(huì)周期性的檢查global runqueue并運(yùn)行其中的goroutine耍缴,否則global runqueue上的goroutine永遠(yuǎn)無(wú)法執(zhí)行。

調(diào)度分配不均:如下圖所示

goroutine1.png

P所分配的任務(wù)G很快就執(zhí)行完了(分配不均)挽霉,這就導(dǎo)致了這個(gè)處理器P很忙防嗡,但是其他的P還有任務(wù),此時(shí)如果global runqueue沒有任務(wù)G了侠坎,那么P不得不從其他的P里拿一些G來(lái)執(zhí)行蚁趁。一般來(lái)說(shuō),如果P從其他的P那里要拿任務(wù)的話实胸,一般就拿run queue的一半 他嫡,這就確保了每個(gè)OS線程都能充分的使用。

使用goroutine

goroutine異常捕獲

package main

import (
    "fmt"
    "time"
)

func addele(a []int ,i int)  {
    defer func() {    //匿名函數(shù)捕獲錯(cuò)誤
        err := recover()
        if err != nil {
            fmt.Println(err)
        }
    }()
a[i]=i
fmt.Println(a)
}

func main() {
    Arry := make([]int,4)
    for i :=0 ; i<10 ;i++{
        go addele(Arry,i)
    }
    time.Sleep(time.Second * 2)
}

同步的goroutine

由于goroutine是異步執(zhí)行的庐完,那很有可能出現(xiàn)主程序退出時(shí)還有g(shù)oroutine沒有執(zhí)行完钢属,此時(shí)goroutine也會(huì)跟著退出。此時(shí)如果想等到所有g(shù)oroutine任務(wù)執(zhí)行完畢才退出门躯,go提供了sync包和channel來(lái)解決同步問(wèn)題淆党,當(dāng)然如果你能預(yù)測(cè)每個(gè)goroutine執(zhí)行的時(shí)間,你還可以通過(guò)time.Sleep方式等待所有的groutine執(zhí)行完成以后在退出程序(如上面的列子)。

示例一:使用sync包同步goroutine

sync大致實(shí)現(xiàn)方式:
WaitGroup 等待一組goroutinue執(zhí)行完畢. 主程序調(diào)用 Add 添加等待的goroutinue數(shù)量. 每個(gè)goroutinue在執(zhí)行結(jié)束時(shí)調(diào)用 Done 染乌,此時(shí)等待隊(duì)列數(shù)量減1.山孔,主程序通過(guò)Wait阻塞,直到等待隊(duì)列為0.

package main

import (
    "fmt"
    "sync"
)

func cal(a int , b int ,n *sync.WaitGroup)  {
    c := a+b
    fmt.Printf("%d + %d = %d\n",a,b,c)
    defer n.Done() //goroutinue完成后, WaitGroup的計(jì)數(shù)-1

}

func main() {
    var go_sync sync.WaitGroup //聲明一個(gè)WaitGroup變量
    for i :=0 ; i<10 ;i++{
        go_sync.Add(1) // WaitGroup的計(jì)數(shù)加1
        go cal(i,i+1,&go_sync)  
    }
    go_sync.Wait()  //等待所有g(shù)oroutine執(zhí)行完畢
}

示例二:通過(guò)channel實(shí)現(xiàn)goroutine之間的同步荷憋。

channel實(shí)現(xiàn)方式:
通過(guò)channel能在多個(gè)groutine之間通訊台颠,當(dāng)一個(gè)goroutine完成時(shí)候向channel發(fā)送退出信號(hào),等所有g(shù)oroutine退出時(shí)候,利用for循環(huán)channel,取channel中的信號(hào)勒庄,若取不到數(shù)據(jù)便會(huì)阻塞原理串前,等待所有g(shù)oroutine執(zhí)行完畢,使用該方法有個(gè)前提是你已經(jīng)知道了你啟動(dòng)了多少個(gè)goroutine锅铅。

package main

import (
    "fmt"
    "time"
)

func cal(a int , b int ,Exitchan chan bool)  {
    c := a+b
    fmt.Printf("%d + %d = %d\n",a,b,c)
    time.Sleep(time.Second*2)
    Exitchan <- true
}

func main() {

    Exitchan := make(chan bool,10)  //聲明并分配管道內(nèi)存
    for i :=0 ; i<10 ;i++{
        go cal(i,i+1,Exitchan)
    }
    for j :=0; j<10; j++{   
        <- Exitchan  //取信號(hào)數(shù)據(jù)酪呻,如果取不到則會(huì)阻塞
    }
    close(Exitchan) // 關(guān)閉管道
}

goroutine之間的通訊

goroutine本質(zhì)上是協(xié)程,可以理解為不受內(nèi)核調(diào)度盐须,而受go調(diào)度器管理的線程玩荠。goroutine之間可以通過(guò)channel進(jìn)行通信或者說(shuō)是數(shù)據(jù)共享,當(dāng)然你也可以使用全局變量來(lái)進(jìn)行數(shù)據(jù)共享贼邓。
示例代碼:采用生產(chǎn)者和消費(fèi)者模式

package main

import (
    "fmt"
    "sync"
)

func Productor(mychan chan int,data int,wait *sync.WaitGroup)  {
    mychan <- data
    fmt.Println("product data:",data)
    defer wait.Done()
}
func Consumer(mychan chan int,wait *sync.WaitGroup)  {
    a := <- mychan
    fmt.Println("consumer data:",a)
    defer wait.Done()
}
func main() {

    datachan := make(chan int, 100)   //通訊數(shù)據(jù)管道
    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        go Productor(datachan, i,&wg) //生產(chǎn)數(shù)據(jù)
        wg.Add(1)
    }
    for j := 0; j < 10; j++ {
        go Consumer(datachan,&wg)  //消費(fèi)數(shù)據(jù)
        wg.Add(1)
    }
    wg.Wait()
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末阶冈,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子塑径,更是在濱河造成了極大的恐慌女坑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,204評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件统舀,死亡現(xiàn)場(chǎng)離奇詭異匆骗,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)誉简,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,091評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門碉就,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人闷串,你說(shuō)我怎么就攤上這事瓮钥。” “怎么了烹吵?”我有些...
    開封第一講書人閱讀 164,548評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵碉熄,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我肋拔,道長(zhǎng)锈津,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,657評(píng)論 1 293
  • 正文 為了忘掉前任凉蜂,我火速辦了婚禮琼梆,結(jié)果婚禮上七咧,老公的妹妹穿的比我還像新娘。我一直安慰自己叮叹,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,689評(píng)論 6 392
  • 文/花漫 我一把揭開白布爆存。 她就那樣靜靜地躺著蛉顽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪先较。 梳的紋絲不亂的頭發(fā)上携冤,一...
    開封第一講書人閱讀 51,554評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音闲勺,去河邊找鬼曾棕。 笑死,一個(gè)胖子當(dāng)著我的面吹牛菜循,可吹牛的內(nèi)容都是我干的翘地。 我是一名探鬼主播,決...
    沈念sama閱讀 40,302評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼癌幕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼衙耕!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起勺远,我...
    開封第一講書人閱讀 39,216評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤橙喘,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后胶逢,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體厅瞎,經(jīng)...
    沈念sama閱讀 45,661評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,851評(píng)論 3 336
  • 正文 我和宋清朗相戀三年初坠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了和簸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,977評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡某筐,死狀恐怖比搭,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情南誊,我是刑警寧澤身诺,帶...
    沈念sama閱讀 35,697評(píng)論 5 347
  • 正文 年R本政府宣布,位于F島的核電站抄囚,受9級(jí)特大地震影響霉赡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜幔托,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,306評(píng)論 3 330
  • 文/蒙蒙 一穴亏、第九天 我趴在偏房一處隱蔽的房頂上張望蜂挪。 院中可真熱鬧,春花似錦嗓化、人聲如沸棠涮。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,898評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)严肪。三九已至,卻和暖如春谦屑,著一層夾襖步出監(jiān)牢的瞬間驳糯,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,019評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工氢橙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留酝枢,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,138評(píng)論 3 370
  • 正文 我出身青樓悍手,卻偏偏與公主長(zhǎng)得像帘睦,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子坦康,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,927評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容