調度器javascript
主要基於三個基本對象上,G,M,P(定義在源碼的src/runtime/runtime.h文件中)php
1. G表明一個goroutine對象,每次go調用的時候,都會建立一個G對象java
2. M表明一個線程,每次創建一個M的時候,都會有一個底層線程創建;全部的G任務,最終還是在M上執行linux
3. P表明一個處理器,每個運行的M都必須綁定一個P,就像線程必須在麼一個CPU核上執行一樣nginx
P的個數就是GOMAXPROCS(最大256),啓動時固定的,通常不修改; M的個數和P的個數不必定同樣多(會有休眠的M或者不須要太多的M)(最大10000);每個P保存着本地G任務隊列,也有一個全局G任務隊列;golang
以下圖所示web
全局G任務隊列會和各個本地G任務隊列按照必定的策略互相交換(滿了,則把本地隊列的一半送給全局隊列)算法
P是用一個全局數組(255)來保存的,而且維護着一個全局的P空閒鏈表編程
每次go調用的時候,都會:windows
1. 建立一個G對象,加入到本地隊列或者全局隊列
2. 若是還有空閒的P,則創建一個M
3. M會啓動一個底層線程,循環執行能找到的G任務
4. G任務的執行順序是,先從本地隊列找,本地沒有則從全局隊列找(一次性轉移(全局G個數/P個數)個,再去其它P中找(一次性轉移一半),
5. 以上的G任務執行是按照隊列順序(也就是go調用的順序)執行的。(這個地方是否是以爲很奇怪??)
對於上面的第2-3步,建立一個M,其過程:
1. 先找到一個空閒的P,若是沒有則直接返回,(哈哈,這個地方就保證了進程不會佔用超過本身設定的cpu個數)
2. 調用系統api建立線程,不一樣的操做系統,調用不同,其實就是和c語言建立過程是一致的,(windows用的是CreateThread,linux用的是clone系統調用),(*^__^*)嘻嘻……
3. 而後創建的這個線程裏面纔是真正作事的,循環執行G任務
那就會有個問題,若是一個系統調用或者G任務執行太長,他就會一直佔用這個線程,因爲本地隊列的G任務是順序執行的,其它G任務就會阻塞了,怎樣停止長任務的呢?(這個地方我找了很久~o(╯□╰)o)
這樣滴,啓動的時候,會專門建立一個線程sysmon,用來監控和管理,在內部是一個循環:
1. 記錄全部P的G任務計數schedtick,(schedtick會在每執行一個G任務後遞增)
2. 若是檢查到 schedtick一直沒有遞增,說明這個P一直在執行同一個G任務,若是超過必定的時間(10ms),就在這個G任務的棧信息裏面加一個標記
3. 而後這個G任務在執行的時候,若是遇到非內聯函數調用,就會檢查一次這個標記,而後中斷本身,把本身加到隊列末尾,執行下一個G
4. O(∩_∩)O哈哈~,若是沒有遇到非內聯函數(有時候正常的小函數會被優化成內聯函數)調用的話,那就慘了,會一直執行這個G任務,直到它本身結束;若是是個死循環,而且GOMAXPROCS=1的話,恭喜你,夯住了!親測,的確如此
對於一個G任務,中斷後的恢復過程:
1. 中斷的時候將寄存器裏的棧信息,保存到本身的G對象裏面
2. 當再次輪到本身執行時,將本身保存的棧信息複製到寄存器裏面,這樣就接着上次以後運行了。 ~\(≧▽≦)/~
可是還有一個問題,就是系統啓動的過程,雨痕沒有說的太明白,我一直有不少問題都狠疑惑(第一個M怎麼來的?,G怎麼找到對應的P?等等),這個讓我蛋疼了很久~
不過我本身意淫了一下,補充在下面,歡迎你們指正
1. 系統啓動的時候,首先跑的是主線程,那第一個M應該就是主線程吧(按照C語言的理解,嘿嘿),這裏叫M1,能夠看前面的圖
2. 而後這個主線程會綁定第一個P1
3. 咱們寫的main函數,其實是做爲一個goroutine來執行的(雨痕說的)
4. 也就是第一個P1就有了一個G1任務,而後第一個M1就執行這個G1任務(也就是main函數),創建這個G1的時候不用創建M了,因爲已經有了M1
5. 這個main函數裏面全部的goroutine,都綁定到當前的M1所對應的P1上,O(∩_∩)O哈哈~
6. 而後創建main裏的goroutine的時候(好比G2),就會創建新的M2,新的M2裏的初始P2的本地任務隊列是空的,會從P1裏面取一些過來,哈哈
7. 這樣兩個M1,M2各自執行本身的G任務,再依次往復,這下就圓滿了~~~
綜上:
因此goroutine是按照搶佔式調度的,一個goroutine最多執行10ms就會換做下一個
這個和目前主流系統的的cpu調度相似(按照時間分片)
windows:20ms
linux:5ms-800ms
1. 在Golang中編譯器也會嘗試進行內聯,將小函數直接複製並編譯,爲了內聯,儘可能消除編譯器沒法偵測的dead code,利用gobuild -gcflags=-m編譯命令能夠查看程序內聯狀態,不得不說golang的編譯工具鏈還是很強大的,十分有利於程序的優化。
Gorutine從入隊到執行
當咱們建立一個G對象,就是
gorutine
,它會加入到本地隊列或者全局隊列若是還有空閒的P,則建立一個M 綁定該 P ,注意!這裏,P 此前必須還沒綁定過M 的,不然不知足空閒的條件。細節點:
不管在哪一個 M 中建立了一個 G,只要 P 有空閒的,就會引發新 M 的建立
不需考慮當前所在 M 中所綁的 P 的 G 隊列是否已滿
新建立的 M 所綁的 P 的初始化隊列會從其餘 G 隊列中取任務過來
先找到一個空閒的P,若是沒有則直接返回
P 個數不會佔用超過本身設定的cpu個數
P 在被 M 綁定後,就會初始化本身的 G 隊列,此時是一個
空隊列
注意這裏的
一個點
!這裏留下第一個問題:若是一個G任務執行時間太長,它就會一直佔用 M 線程,因爲隊列的G任務是順序執行的,其它G任務就會阻塞,如何避免該狀況發生?--①
M 會啓動一個
底層線程
,循環執行
能找到的 G 任務。這裏的尋找的 G 從下面幾方面找:當前 M 所綁的 P 隊列中找
去別的 P 的隊列中找
去全局 G 隊列中找
G任務的執行順序是,先從本地隊列找,本地沒有則從全局隊列找
程序啓動的時候,首先跑的是主線程,而後這個主線程會綁定第一個 P
入口 main 函數,實際上是做爲一個 goroutine 來執行
解答問題-①
協程的切換時間片是10ms,也就是說 goroutine 最多執行10ms就會被 M 切換到下一個 G。這個過程,又被稱爲 中斷,掛起
原理:
go程序啓動時會首先建立一個特殊的內核線程 sysmon
,用來監控和管理,其內部是一個循環:
記錄全部 P 的 G 任務的
計數 schedtick
,schedtick會在每執行一個G任務後遞增若是檢查到
schedtick
一直沒有遞增,說明這個 P 一直在執行同一個 G 任務,若是超過10ms,就在這個G任務的棧信息裏面加一個 tag 標記而後這個 G 任務在執行的時候,若是遇到非內聯函數調用,就會檢查一次這個標記,而後中斷本身,把本身加到隊列末尾,執行下一個G
若是沒有遇到
非內聯函數
調用的話,那就會一直執行這個G任務,直到它本身結束;若是是個死循環,而且 GOMAXPROCS=1 的話。那麼一直只會只有一個 P 與一個 M,且隊列中的其餘 G 不會被執行!
例子,下面的這段代碼,hello world
不會被輸出
func main(){
runtime.GOMAXPROCS(1)
go func(){
fmt.Println("hello world")
}()
go func(){
for {
}
}()
select {}
}
中斷後的恢復
中斷的時候將寄存器裏的棧信息,保存到本身的 G 對象裏面
當再次輪到本身執行時,將本身保存的棧信息複製到寄存器裏面,這樣就接着上次以後運
GOMAXPROCS--性能調優
看完上面的內容,相信你已經知道,GOMAXPROCS
就是 go 中 runtime 包的一個函數。它設置了 P 的最多的個數。這也就直接致使了 M 最多的個數是多少,而 M 的個數就決定了各個 G 隊列能同時被多少個 M 線程來進行調取執行!
故,咱們通常將 GOMAXPROCS 的個數設置爲 CPU 的核數,且須要注意的是:
go 1.5 版本以前的 GOMAXPROCS 默認是 1
go 1.5 版本以後的 GOMAXPROCS 默認是 Num of cpu
二、Coroutine簡介
Coroutine(協程)是一種用戶態的輕量級線程,特色以下:
A、輕量級線程
B、非搶佔式多任務處理,由協程主動交出控制權。
C、編譯器/解釋器/虛擬機層面的任務
D、多個協程可能在一個或多個線程上運行。
E、子程序是協程的一個特例。
不一樣語言對協程的支持:
A、C++經過Boost.Coroutine實現對協程的支持
B、Java不支持
C、Python經過yield關鍵字實現協程,Python3.5開始使用async def對原生協程的支持
三、goroutine簡介
在Go語言中,只須要在函數調用前加上關鍵字go便可建立一個併發任務單元,新建的任務會被放入隊列中,等待調度器安排。
進程在啓動的時候,會建立一個主線程,主線程結束時,程序進程將終止,所以,進程至少有一個線程。main函數裏,必須讓主線程等待,確保進程不會被終止。
go語言中併發指的是讓某個函數獨立於其它函數運行的能力,一個goroutine是一個獨立的工做單元,Go的runtime(運行時)會在邏輯處理器上調度goroutine來運行,一個邏輯處理器綁定一個操做系統線程,所以goroutine不是線程,是一個協程。
進程:一個程序對應一個獨立程序空間
線程:一個執行空間,一個進程能夠有多個線程
邏輯處理器:執行建立的goroutine,綁定一個線程
調度器:Go運行時中的,分配goroutine給不一樣的邏輯處理器
全局運行隊列:全部剛建立的goroutine隊列
本地運行隊列:邏輯處理器的goroutine隊列
當建立一個goroutine後,會先存放在全局運行隊列中,等待Go運行時的調度器進行調度,把goroutine分配給其中的一個邏輯處理器,並放到邏輯處理器對應的本地運行隊列中,最終等着被邏輯處理器執行便可。
Go的併發是管理、調度、執行goroutine的方式。
默認狀況下,Go默認會給每一個可用的物理處理器都分配一個邏輯處理器。
能夠在程序開頭使用runtime.GOMAXPROCS(n)設置邏輯處理器的數量。
若是須要設置邏輯處理器的數量,通常採用以下代碼設置:
runtime.GOMAXPROCS(runtime.NumCPU())
對於併發,Go語言自己本身實現的調度,對於並行,與物理處理器的核數有關,多核就能夠並行併發,單核只能併發。
四、goroutinue使用示例
在Go語言中,只須要在函數調用前加上關鍵字go便可建立一個併發任務單元,新建的任務會被放入隊列中,等待調度器安排。
package main
import (
"fmt"
"sync"
)
func main(){
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
fmt.Printf("Hello,Go.This is %d\n", i)
}
}()
go func() {
defer wg.Done()
for i := 0; i < 10000; i++ {
fmt.Printf("Hello,World.This is %d\n", i)
}
}()
wg.Wait()
}
sync.WaitGroup是一個計數的信號量,使main函數所在主線程等待兩個goroutine執行完成後再結束,不然兩個goroutine還在運行時,主線程已經結束。
sync.WaitGroup使用很是簡單,使用Add方法設設置計數器爲2,每個goroutine的函數執行完後,調用Done方法減1。Wait方法表示若是計數器大於0,就會阻塞,main函數會一直等待2個goroutine完成再結束。
五、goroutine的本質
goroutine是輕量級的線程,佔用的資源很是小(Go將每一個goroutine stack的size默認設置爲2k)線程的切換由操做系統控制,而goroutine的切換則由用戶控制。
goroutinue本質上是協程。
goroutinue能夠實現並行,即多個goroutinue能夠在多個處理器同時運行,而協程同一時刻只能在一個處理器上運行。
goroutine之間的通訊是經過channel,而協程的通訊是經過yield和resume()操做。
2、goroutine調度機制
一、線程調度模型
高級語言對內核線程的封裝實現一般有三種線程調度模型:
A、N:1模型。N個用戶空間線程在1個內核空間線程上運行,優點是上下文切換很是快但沒法利用多核系統的優勢。
B、1:1模型。1個內核空間線程運行一個用戶空間線程,充分利用了多核系統的優點但上下文切換很是慢,由於每一次調度都會在用戶態和內核態之間切換。
C、M:N模型。每一個用戶線程對應多個內核空間線程,同時也能夠一個內核空間線程對應多個用戶空間線程,使用任意個內核模型管理任意個goroutine,但缺點是調度的複雜性。
二、Go調度器簡介
Go的最小調度單元爲goroutine,但操做系統最小的調度單元依然是線程,因此go調度器(go scheduler)要作的工做是如何將衆多的goroutine放在有限的線程上進行高效而公平的調度。
操做系統的調度不失爲高效和公平,好比CFS調度算法。go引入goroutine的核心緣由是goroutine輕量級,不管是從進程到線程,仍是從線程到goroutine,其核心都是爲了使調度單元更加輕量級,能夠輕易建立幾萬幾十萬的goroutine而不用擔憂內存耗盡等問題。go引入goroutine試圖在語言內核層作到足夠高性能得同時(充分利用多核優點、使用epoll高效處理網絡/IO、實現垃圾回收等機制)儘可能簡化編程。
三、Go調度器實現原理
Go 1.1開始,Go scheduler實現了M:N的G-P-M線程調度模型,即任意數量的用戶態goroutine能夠運行在任意數量的內核空間線程線程上,不只可使上線文切換更加輕量級,又能夠充分利用多核優點。
爲了實現M:N線程調度機制,Go引入了3個結構體:
M:操做系統的內核空間線程
G:goroutine對象,G結構體包含調度一個goroutine所須要的堆棧和instruction pointer(IP指令指針),以及其它一些重要的調度信息。每次go調用的時候,都會建立一個G對象。
P:Processor,調度的上下文,實現M:N調度模型的關鍵,M必須拿到P才能對G進行調度,P限定了go調度goroutine的最大併發度。每個運行的M都必須綁定一個P。
P的個數是GOMAXPROCS(最大256),啓動時固定,通常不修改;M的個數和P的個數不必定相同(會有休眠的M或者不須要太多的M);每個P保存着本地G任務隊列,也能使用全局G任務隊列。
全局G任務隊列會和各個本地G任務隊列按照必定的策略互相交換。
P是用一個全局數組(255)來保存的,而且維護着一個全局的P空閒鏈表。
每次調用go的時候,都會:
A、建立一個G對象,加入到本地隊列或者全局隊列
B、若是有空閒的P,則建立一個M
C、M會啓動一個底層線程,循環執行能找到的G任務
D、G任務的執行順序是先從本地隊列找,本地沒有則從全局隊列找(一次性轉移(全局G個數/P個數)個,再去其它P中找(一次性轉移一半)。
E、G任務執行是按照隊列順序(即調用go的順序)執行的。
建立一個M過程以下:
A、先找到一個空閒的P,若是沒有則直接返回。
B、調用系統API建立線程,不一樣的操做系統調用方法不同。
C、 在建立的線程裏循環執行G任務
若是一個系統調用或者G任務執行太長,會一直佔用內核空間線程,因爲本地隊列的G任務是順序執行的,其它G任務就會阻塞。所以,Go程序啓動的時候,會專門建立一個線程sysmon,用來監控和管理,sysmon內部是一個循環:
A、記錄全部P的G任務計數schedtick,schedtick會在每執行一個G任務後遞增。
B、若是檢查到 schedtick一直沒有遞增,說明P一直在執行同一個G任務,若是超過必定的時間(10ms),在G任務的棧信息裏面加一個標記。
C、G任務在執行的時候,若是遇到非內聯函數調用,就會檢查一次標記,而後中斷本身,把本身加到隊列末尾,執行下一個G。
D、若是沒有遇到非內聯函數(有時候正常的小函數會被優化成內聯函數)調用,會一直執行G任務,直到goroutine本身結束;若是goroutine是死循環,而且GOMAXPROCS=1,阻塞。
四、搶佔式調度
Go沒有時間片的概念。若是某個G沒有進行system call調用、沒有進行I/O操做、沒有阻塞在一個channel操做上,M經過搶佔式調度讓長任務G停下來並調度下一個G。
除非極端的無限循環或死循環,不然只要G調用函數,Go runtime就有搶佔G的機會。Go程序啓動時,Go runtime會啓動一個名爲sysmon的M(通常稱爲監控線程),sysmon無需綁定P便可運行。sysmon是GO程序啓動時建立的一個用於監控管理的線程。
sysmon每20us~10ms啓動一次,sysmon主要完成以下工做:
A、釋放閒置超過5分鐘的span物理內存;
B、若是超過2分鐘沒有垃圾回收,強制執行;
C、將長時間未處理的netpoll結果添加到任務隊列;
D、向長時間運行的G任務發出搶佔調度;
E、收回因syscall長時間阻塞的P;
若是一個G任務運行10ms,sysmon就會認爲其運行時間過久而發出搶佔式調度的請求。一旦G的搶佔標誌位被設爲true,那麼待G下一次調用函數或方法時,runtime即可以將G搶佔,並移出運行狀態,放入P的local runq中,等待下一次被調度。
3、runtime包
一、Gosched
runtime.Gosched()用於讓出CPU時間片,讓出當前goroutine的執行權限,調度器安排其它等待的任務運行,並在下次某個時候從該位置恢復執行。
二、Goexit
調用runtime.Goexit()將當即終止當前goroutine執⾏,調度器確保全部已註冊defer延遲調用被執行。
三、GOMAXPROCS
調用runtime.GOMAXPROCS()用來設置能夠並行計算的CPU核數的最大值,並返回設置前的值。
4、Channel通道
一、Channel簡介
Channel是goroutine之間通訊的通道,用於goroutine之間發消息和接收消息。Channel是一種引用類型的數據,能夠做爲參數,也能夠做爲返回值。
二、Channel的建立
channel聲明使用chan關鍵字,channel的建立須要指定通道中發送和接收數據的類型。
使用make來創建一個信道:
var channel chan int = make(chan int)
// 或channel := make(chan int)
make有第二個參數,用於指定通道的大小。
三、Channel的操做
//發送數據:寫
channel<- data
//接收數據:讀
data := <- channel
關閉通道:發送方關閉通道,用於通知接收方已經沒有數據
關閉通道後,其它goroutine訪問通道獲取數據時,獲得零值和false
有條件結束死循環:
for{
v ,ok := <- chan
if ok== false{
//通道已經關閉。。
break
}
}
//循環從通道中獲取數據,直到通道關閉。
for v := range channel{
//從通道讀取數據
}
Channel使用示例以下:
package main
import (
"fmt"
"time"
)
type Person struct {
name string
age uint8
address Address
}
type Address struct {
city string
district string
}
func SendMessage(person *Person, channel chan Person){
go func(person *Person, channel chan Person) {
fmt.Printf("%s send a message.\n", person.name)
channel<-*person
for i := 0; i < 5; i++ {
channel<- *person
}
close(channel)
fmt.Println("channel is closed.")
}(person, channel)
}
func main() {
channel := make(chan Person,1)
harry := Person{
"Harry",
30,
Address{"London","Oxford"},
}
go SendMessage(&harry, channel)
data := <-channel
fmt.Printf("main goroutine receive a message from %s.\n", data.name)
for {
i, ok := <-channel
time.Sleep(time.Second)
if !ok {
fmt.Println("channel is empty.")
break
}else{
fmt.Printf("receive %s\n",i.name)
}
}
}
結果以下:
Harry send a message.
main goroutine receive a message from Harry.
receive Harry
receive Harry
receive Harry
channel is closed.
receive Harry
receive Harry
channel is empty.
Go運行時系統並無在通道channel被關閉後當即把false做爲相應接收操做的第二個結果,而是等到接收端把已在通道中的全部元素值都接收到後才這樣作,確保在發送端關閉通道的安全性。
被關閉的通道會禁止數據流入, 是隻讀的,仍然能夠從關閉的通道中取出數據,但不能再寫入數據。
給一個nil的channel發送數據,形成永遠阻塞 ;從一個nil的channel接收數據,形成永遠阻塞。給一個已經關閉的channel發送數據,引發panic ;
從一個已經關閉的channel接收數據,返回帶緩存channel中緩存的值,若是通道中無緩存,返回0。
四、無緩衝通道
make建立通道時,默認沒有第二個參數,通道的大小爲0,稱爲無緩衝通道。
無緩衝的通道是指通道的大小爲0,即通道在接收前沒有能力保存任何值,無緩衝通道發送goroutine和接收gouroutine必須是同步的,若是沒有同時準備好,先執行的操做就會阻塞等待,直到另外一個相對應的操做準備好爲止。無緩衝通道也稱爲同步通道。
無緩衝的信道永遠不會存儲數據,只負責數據的流通。從無緩衝信道取數據,必需要有數據流進來才能夠,不然當前goroutine會阻塞;數據流入無緩衝信道, 若是沒有其它goroutine來拿取走數據,那麼當前goroutine會阻塞。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
var sum int = 0
for i := 0; i < 10; i++ {
sum += i
}
//發送數據到通道
ch <- sum
}()
//從通道接收數據
fmt.Println(<-ch)
}
在計算sum和的goroutine沒有執行完,將值賦發送到ch通道前,fmt.Println(<-ch)會一直阻塞等待,main函數所在的主goroutine就不會終止,只有當計算和的goroutine完成後,而且發送到ch通道的操做準備好後,main函數的<-ch會接收計算好的值,而後打印出來。
無緩存通道的發送數據和讀取數據的操做不能放在同一個協程中,防止發生死鎖。一般,先建立一個goroutine對通道進行操做,此時新建立goroutine會阻塞,而後再在主goroutine中進行通道的反向操做,實現goroutine解鎖,即必須goroutine在前,解鎖goroutine在後。
五、有緩衝通道
make建立通道時,指定通道的大小時,稱爲有緩衝通道。
對於帶緩存通道,只要通道中緩存不滿,能夠一直向通道中發送數據,直到緩存已滿;同理只要通道中緩存不爲0,能夠一直從通道中讀取數據,直到通道的緩存變爲0纔會阻塞。
相對於不帶緩存通道,帶緩存通道不易形成死鎖,能夠同時在一個goroutine中放心使用。
帶緩存通道不只能夠流通數據,還能夠緩存數據,當帶緩存通道達到滿的狀態的時候纔會阻塞,此時帶緩存通道不能再承載更多的數據。
帶緩存通道是先進先出的。
六、單向通道
對於某些特殊的場景,須要限制一個通道只能夠接收,不能發送;限制一個通道只能發送,不能接收。只能單向接收或發送的通道稱爲單向通道。
定義單向通道只須要在定義的時候,帶上<-便可。
var send chan<- int //只能發送
var receive <-chan int //只能接收
<-操做符的位置在後面只能發送,對應發送操做;<-操做符的位置在前面只能接收,對應接收操做。
單向通道一般用於函數或者方法的參數。
5、channel應用
一、廣播功能實現
當一個通道關閉時, 全部對此通道的讀取的goroutine都會退出阻塞。
package main
import (
"fmt"
"time"
)
func notify(id int, channel chan int){
<-channel//接收到數據或通道關閉時退出阻塞
fmt.Printf("%d receive a message.\n", id)
}
func broadcast(channel chan int){
fmt.Printf("Broadcast:\n")
close(channel)//關閉通道
}
func main(){
channel := make(chan int,1)
for i:=0;i<10 ;i++ {
go notify(i,channel)
}
go broadcast(channel)
time.Sleep(time.Second)
}
二、select使用
select用於在多個channel上同時進行偵聽並收發消息,當任何一個case知足條件時即執行,若是沒有可執行的case則會執行default的case,若是沒有指定default case,則會阻塞程序。select的語法以下:
select {
case communication clause :
statement(s);
case communication clause :
statement(s);
/*能夠定義任意數量的 case */
default : /*可選 */
statement(s);
}
Select多路複用中:
A、每一個case都必須是一次通訊
B、全部channel表達式都會被求值
C、全部被髮送的表達式都會被求值
D、若是任意某個通訊能夠進行,它就執行;其它被忽略。
E、若是有多個case均可以運行,Select會隨機公平地選出一個執行。其它不會執行。
F、不然,若是有default子句,則執行default語句。若是沒有default子句,select將阻塞,直到某個通訊能夠運行;Go不會從新對channel或值進行求值。
package main
import (
"fmt"
"time"
)
func doWork(channels *[10]chan int){
for {
select {
case x1 := <-channels[0]:
fmt.Println("receive x1: ",x1)
case x2 := <-channels[1]:
fmt.Println("receive x2: ",x2)
case x3 := <-channels[2]:
fmt.Println("receive x3: ",x3)
case x4 := <-channels[3]:
fmt.Println("receive x4: ",x4)
case x5 := <-channels[4]:
fmt.Println("receive x5: ",x5)
case x6 := <-channels[5]:
fmt.Println("receive x6: ",x6)
case x7 := <-channels[6]:
fmt.Println("receive x7: ",x7)
case x8 := <-channels[7]:
fmt.Println("receive x8: ",x8)
case x9 := <-channels[8]:
fmt.Println("receive x9: ",x9)
case x10 := <-channels[9]:
fmt.Println("receive x10: ",x10)
}
}
}
func main(){
var channels [10]chan int
go doWork(&channels)
for i := 0; i < 10; i++ {
channels[i] = make(chan int,1)
channels[i]<- i
}
time.Sleep(time.Second*5)
}
結果以下:
receive x4: 3
receive x10: 9
receive x9: 8
receive x5: 4
receive x2: 1
receive x7: 6
receive x8: 7
receive x1: 0
receive x3: 2
receive x6: 5
6、死鎖
Go程序中死鎖是指全部的goroutine在等待資源的釋放。
一般,死鎖的報錯信息以下:fatal error: all goroutines are asleep - deadlock!
Goroutine死鎖產生的緣由以下:
A、只在單一的goroutine裏操做無緩衝信道,必定死鎖
B、非緩衝信道上若是發生流入無流出,或者流出無流入,會致使死鎖
所以,解決死鎖的方法有:
A、取走無緩衝通道的數據或是發送數據到無緩衝通道
B、使用緩衝通道
本文分享自微信公衆號 - golang算法架構leetcode技術php(golangLeetcode)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。