Goroutine
Goroutine可以被看作是Go語言特有的應(yīng)用程序線程,
傳統(tǒng)的線程通訊:將數(shù)據(jù)存放在共享內(nèi)存中口糕,供多個線程中的程序訪問。雖然在思路省操作非常簡單,但卻使并發(fā)控制變得相對麻煩演熟。只有做到了各種約束和限制,才可以使這種方法實施司顿。
go語言處理方法:不推薦使用共享內(nèi)存區(qū)的方法傳遞數(shù)據(jù)芒粹,作為代替,因該優(yōu)先使用Channel大溜,Channel主要被用來在多個Goroutione之間傳遞數(shù)據(jù)化漆,并且還會保證其過程的同步。作為一種可選方法钦奋,Go語言依然提供了一些傳統(tǒng)的并發(fā)訪問控制方法座云。
Go的線程實現(xiàn)模型疙赠,有三個必知的核心元素
M:machine的縮寫。一個M代表一個內(nèi)核線程朦拖,或者“工作線程”圃阳。
P:processor的縮寫。一個P代表執(zhí)行一個Go代碼片段所必需的資源(或稱“上下文環(huán)境”)璧帝。
G:goroutine的縮寫捍岳。一個G代表一個Go代碼片段。前者是對后者的一種封裝睬隶。
簡單來說锣夹,一個G的執(zhí)行需要P和M的支持。一個M在與一個P關(guān)聯(lián)之后苏潜,就形成了一個有效的G運行環(huán)境(內(nèi)核線程+上下文環(huán)境)晕城。
每個P都會包含一個可運行的G的隊列(runq)。該隊列中的G會被依次傳遞給與本地P關(guān)聯(lián)的M窖贤,并獲得運行時機砖顷。
在這里,我把運行當前G的那個M稱為“當前M”赃梧,并把與當前M關(guān)聯(lián)的那個P稱為“本地P”
Goroution的運行流程(主線程)
1. 主線程Goroution是在runtime.m0(一個內(nèi)核線程)上被運行的滤蝠,
引導程序runtime.go就是在runtime.m0(一個內(nèi)核線程)上運行,運行完runtime才會接著運行主線程授嘀。
2. 主線程Goroution設(shè)定每一個Goroutine所能申請的椢锟龋空間的最大尺寸灼舍。32位為250M背亥,64位為1GB。如果某一個Goroutine超出這個界限就會報出"stack overflow"的運行時恐慌问拘,程序的運行就會終止巷折。
3. 主線程Goroution會啟動系統(tǒng)檢測器(對調(diào)度器的工作進行查漏補缺)压鉴。
4. 進行一系列初始化工作。(這些操作比較特殊锻拘,這時會將主線程Goroution與runtime.m0進行鎖定油吭,)
a.創(chuàng)建一個特殊的defer語句,以執(zhí)行主Goroution退出時所做的善后處理(與當前的m進行解鎖操作署拟,防止非正常結(jié)束造成死鎖)
b.檢查當前M是否為runtime.m0婉宰。如果不是,說明此時程序出了問題推穷。這時就會立即拋出異常心包,主程序停止。
c.創(chuàng)建定時垃圾回收器(scavenger)馒铃。主程序會創(chuàng)建一個專門的Goroution來封裝這個定時垃圾回收器蟹腾,并將它放入當前的M可運行的P隊列中痕惋。定時垃圾回收會定時(當前是兩分鐘執(zhí)行一次)的執(zhí)行垃圾回收任務(wù),并在必要的時候促使一些M協(xié)助他進行一些垃圾回收
d.執(zhí)行main的init函數(shù)岭佳。
e.對之前的那個特殊defer語句進行最后的檢查和設(shè)置,并必要時拋出異常萧锉。
5. 執(zhí)行main函數(shù)珊随。
6. 檢查是否有Goroution發(fā)生了運行時恐慌,并進行必要處理柿隙。
7. 主線程Goroution結(jié)束自己及當前進程的運行叶洞。
runtime包和Groutine
看了主流程就會發(fā)現(xiàn)runtime包對Groutine有好多設(shè)置的操作。
runtime.GOMAXPROCS函數(shù)
通過runtime.GOMAXPROCS函數(shù)禀崖,應(yīng)用程序何以在運行期間設(shè)置運行時系統(tǒng)中得P最大數(shù)量衩辟。但這會引起“Stop the Word”。所以波附,應(yīng)在應(yīng)用程序最早的調(diào)用艺晴。并且最好的設(shè)置P最大值的方法是在運行Go程序之前設(shè)置好操作程序的環(huán)境變量GOMAXPROCS,而不是在程序中調(diào)用runtime.GOMAXPROCS函數(shù)。
最后記住掸屡,無論我們傳遞給函數(shù)的整數(shù)值是什么值封寞,運行時系統(tǒng)的P最大值總會在1~256之間。
runtime.Goexit函數(shù)
runtime.Goexit函數(shù)被調(diào)用后仅财,會立即使調(diào)用他的Groution的運行被終止狈究,但其他Goroutine并不會受到影響。runtime.Goexit函數(shù)在終止調(diào)用它的Goroutine的運行之前會先執(zhí)行該Groution中還沒有執(zhí)行的defer語句盏求。
runtime.Gosched函數(shù)
runtime.Gosched函數(shù)的作用是暫停調(diào)用他的Goroutine的運行抖锥,調(diào)用他的Goroutine會被重新置于Gorunnable狀態(tài),并被放入調(diào)度器可運行G隊列中碎罚。
runtime.NumGoroutine函數(shù)
runtime.NumGoroutine函數(shù)在被調(diào)用后磅废,會返回系統(tǒng)中的處于特定狀態(tài)的Goroutine的數(shù)量。這里的特指是指Grunnable\Gruning\Gsyscall\Gwaition荆烈。處于這些狀態(tài)的Groutine即被看做是活躍的或者說正在被調(diào)度还蹲。
注意:垃圾回收所在Groutine的狀態(tài)也處于這個范圍內(nèi)的話,也會被納入該計數(shù)器耙考。
runtime.LockOSThread和runtime.UnlockOSThread函數(shù)
前者調(diào)用會使調(diào)用他的Goroutine與當前運行它的M鎖定到一起谜喊,后者調(diào)用會解除這樣的鎖定。
注意:
- 多次調(diào)用前者不會出現(xiàn)任何問題倦始,但最后一次調(diào)用的記錄會被保留斗遏,
- 即時之前沒有調(diào)用前者,對后者的調(diào)用也不會產(chǎn)生任何副作用
debug.SetMaxStack函數(shù)
debug.SetMaxStack函數(shù)的功能是約束單個Groutine所能申請的椥兀空間的最大尺寸诵次。
debug.SetMaxThreads函數(shù)
debug.SetMaxThreads函數(shù)的功能是對go語言運行時系統(tǒng)所使用的內(nèi)核線程的數(shù)量(確切的說是M的數(shù)量)進行設(shè)置
runtime.GC函數(shù)
會讓運行時系統(tǒng)進行一次強制性的垃圾收集账蓉,
- 強制的垃圾回收:不管怎樣,都要進行的垃圾回收逾一。
- 非強制的垃圾回收:只會在一定條件下進行的垃圾回收(即運行時铸本,系統(tǒng)自上次垃圾回收之后新申請的堆內(nèi)存的單元(也成為單元增量)達到指定的數(shù)值)。
debug.SetGCPercent函數(shù)
用于設(shè)置一個比率(垃圾收集比率)遵堵,前面所說的單元增量與前一次垃圾收集時的歲內(nèi)存的單元數(shù)量和此垃圾手機比率有關(guān)箱玷。
<觸發(fā)垃圾收集的堆內(nèi)存單元增量>=<上一次垃圾收集完的堆內(nèi)存單元數(shù)量>*(<垃圾收集比率>/100)
Channel
go語言所提倡的“應(yīng)該以通訊手段來共享內(nèi)”的最直接和最重要的體現(xiàn)。即通道類型陌宿,是go語言預定義的數(shù)據(jù)類型之一锡足。
在同一時刻,僅有一個Goroutine能向一個通道發(fā)送元素值壳坪,同時也僅有一個Groutine能從他那里接收元素值舶得。在通道中,各個元素值都是嚴格按照發(fā)送至此的先后順序排列的爽蝴。最早發(fā)送至通道的元素都會被最先接收沐批。因此相當于(先進先出的)一個消息隊列。
初始化
make(chan int,10)
第一個參數(shù)表明此值的具體類型是元素類型為int的通道類型蝎亚,第二個參數(shù)則指出該值在同一時刻最多可以容納10個元素值珠插。也就是說,如果我們發(fā)送給該通道的值尚未背取走颖对,那么該通道做多可以暫存10個元素值捻撑。
make(chan int)
我們對初始化一個字典類型的時候時候也可以類似這樣做。不過這樣做的效果卻截然不同缤底。
- 字典類型:
是否傳遞第二個值顾患,只會影響到該值的初始長度。其初始長度與第二個長度相同个唧,或者為零江解。由于字典類型的值是可以自動增長的,所以通常不會出現(xiàn)什么問題徙歼,
- 通道類型:
一個通道類型的值的緩沖容量是固定不變的犁河,它可同時容納的元素值的最大值永遠等于他被初始化是給定的第二個參數(shù)值。如果第二個值被省略了魄梯,那么就表示被初始化的這個通道無法緩沖任何元素值桨螺,發(fā)送給他的元素值必須立即取走。
實際上酿秸,我們把初始化時給定了第二個參數(shù)的通道灭翔,稱為緩沖通道,未給定第二個參數(shù)的通道稱為非緩沖通道辣苏。
Happens before
如果一個通道是帶緩沖的:
- 針對此通道的發(fā)送操作會被阻塞肝箱,直到被發(fā)送的元素完全被復制到通道的緩沖區(qū)中哄褒。(通道緩沖元素的這個動作一定發(fā)生在相應(yīng)的發(fā)送動作之前。)
- 針對此通道的接收操作會阻塞煌张,直到當前Goroutine真正從中獲取到一個元素值呐赡。(當前Groutine接收某個元素的這個結(jié)果一定會在形成在相應(yīng)的接收操作完成之前)
- 對于同一個元素值來說,把他發(fā)送給某個通道的操作骏融,一定會在從該通道接收它的操作完成之前完成链嘀。
接收元素
strChan:=make(chan string,3)
elem:=<-strChan
如果通道中沒有任何值,當前的Groutine會被阻塞到這里绎谦,進入Gwaiting狀態(tài)管闷,直到Groutine中有新值出現(xiàn)粥脚。如果在接收操作之前或者操作中該通道關(guān)閉了窃肠,那么該操作會立即結(jié)束,并且變量elem會被賦予該通道的元素類型的零值刷允。
elem,ok:=<-strChan
如果通道中沒有任何值冤留,當前的Groutine會被阻塞到這里,進入Gwaiting狀態(tài)树灶,直到Groutine中有新值出現(xiàn)纤怒。如果在接收操作之前或者操作中該通道關(guān)閉了,那么該操作會立即結(jié)束天通,并且變量elem會被賦予該通道的元素類型的零值泊窘。
如果之前通道關(guān)閉了,則ok為flase像寒,否則為true.
注意
試圖從一個未初始化的通道類型值中哪里接元素值會造成當前的Groutine的永久阻塞烘豹!
發(fā)送元素
strChan<"a"
一個通道的緩沖容量是固定的。因此诺祸,當某個Groutine中向通道發(fā)送值的操作的時候携悯,該Groutine會被阻塞在哪里。直到該通道中有足夠的空間容納該元素為止筷笨。
如果有多個Groutine因向一個通道發(fā)送元素值而被阻塞憔鬼,那么當給通道中有多余的空間時,最早被阻塞的那個Groutine會最先被喚醒胃夏。也就是說轴或,喚醒順序和發(fā)送操作順序相同。
無論發(fā)送操作還是接收操作仰禀,每次只會喚醒一個Groutine
注意
- 當我們像一個值為空的通道發(fā)送元素值侮叮,擋圈Groutine會被永久阻塞。
- 我們向一個通道發(fā)送一個值后悼瘾,該通道只會得到該值的一個副本囊榜。
關(guān)閉通道
close(strChan)
不合時宜的關(guān)閉通道會給針對它的發(fā)送和接收操作帶來問題审胸。這樣可能會對他們所在的Groutine的正常流程的執(zhí)行產(chǎn)生影響。因此卸勺,我們要在保證安全的情況下進行關(guān)閉通道的操作砂沛。
更確切的講,調(diào)用close函數(shù)的作用是告訴系統(tǒng)不應(yīng)該再允許對被關(guān)閉的通道進行發(fā)送操作曙求,該通道即將被關(guān)閉碍庵。只能讓相應(yīng)的通道進入關(guān)閉狀態(tài),而不是立即阻止對它的一切操作悟狱。
for與Channel
在需要循環(huán)接收通道值的元素情境下静浴,我們總是應(yīng)該優(yōu)先使用for語句來實現(xiàn)。
當通道中沒有任何元素的時候挤渐,當前Groutine依然會陷入阻塞苹享。阻塞的具體位置會在其中的range子語句中。
語句for會不斷的嘗試從通道中接收元素值浴麻,直到該通道被關(guān)閉得问。
相應(yīng)的通道關(guān)閉后,若通道中已無元素值或當前的Groutine正阻塞在這里软免,則for語句就會立即結(jié)束宫纬。而當此時通道中還有遺留的元素時,運行時體統(tǒng)會等for語句將他們?nèi)拷邮蘸笤俳Y(jié)束該語句的執(zhí)行膏萧。
栗子:
func batch(origs <-chan Person)<-chan Person{
dests:=make(chan Person,100)
go func(){
for p:=range origs {
hander.Handle(p)
dests<-p
}
fmt.Println("All the information has been handled")
close(deste)
}()
return deste
}
這個方法中漓骚,for語句不斷嘗試接收通道origs中的元素值。每接收一個元素值榛泛,for語句中的代碼就會對他進行處理蝌蹂,并通過dests通道將處理的結(jié)果傳遞給下一道工序
select語句與Channal
一個select語句在被執(zhí)行的時候,會選擇執(zhí)行其中的一個分支挟鸠。在表現(xiàn)形式上叉信,select語句與switch語句非常相似,但他們選擇分支的方法完全是不同的艘希。
組成和編寫方法
在select語句中硼身,每個分支依然以關(guān)鍵字case開始。但與switch不同的是覆享,跟在每個case后面的只能是針對某個通道的發(fā)送語句或接收語句佳遂。
select語句也可以包含default case。如果select語句中的所有case都不滿足選擇條件且存在default case撒顿,那么default case就會被執(zhí)行丑罪。
分支選擇規(guī)則
- 在開始執(zhí)行select語句的時候,所有跟在case關(guān)鍵字右邊的發(fā)送語句或接受語句中的通道表達式和元素表達式都會先被求值。求值順序是:自上而下吩屹、從左到右的跪另。
- 其中的通道是可以為nil的,不管他是表達式還是標識符煤搜。但是這樣的話免绿,他所屬的case就會永遠被無視,就像case語句中不包含它一樣擦盾。
- 在執(zhí)行select語句的時候嘲驾,運行系統(tǒng)自上而下地判斷每個case中的發(fā)送或者接收操作是否可以被立即執(zhí)行。這里的立即執(zhí)行的意思是當前的Groutine不會因此操作而阻塞迹卢。
- 當發(fā)現(xiàn)第一個滿足條件的case時辽故,運行系統(tǒng)就會執(zhí)行該case所包含的語句。這也意味著其他case被忽略腐碱。如果多個case滿足條件,那么運行時系統(tǒng)就會通過一個偽隨機的算法決定那個case將會被執(zhí)行誊垢。
- 另一方面,如果被執(zhí)行的select語句中的所有case都不滿足選擇的條件并且沒有default case的話喻杈,那么當前Groutine就會一直被阻塞于此彤枢,直到某一個case 中的發(fā)送或接收操作可以被立即進行為止狰晚。(若這樣的select語句中的所有case右邊的通道都是nil筒饰,那么當前Groutine就會被永久地阻塞在這條語句上)
更多使用習慣
普通避免死鎖
go func() {
for {
select{
case e,ok:=<-ch:
if !ok {
//End
break
}else{
//continue
}
}
}
}()
當通道關(guān)閉就停止
go func() {
var e int
ok:=true
for {
select{
case e,ok=<-ch:
if !ok {
//End
break
}else{
//continue
}
}
}
if !ok{
break
}
}()
流程執(zhí)行超時
1
go func() {
for {
select {
case <- time.NewTimer(time.Millisecond).C:
fmt.Println("time out")
break
}
}
}()
2
go func() {
var e int
ok:=true
for {
select {
case e,ok=<-chaint://執(zhí)行語句
case ok=<-func() chan bool{//發(fā)出超時語句
timeout:=make(chan bool,1)
go func() {
time.Sleep(time.Millisecond)
timeout<-false
}()
return timeout
}():
fmt.Println("timeOut=====")
break
}
if !ok {
break
}
}
}()