技術分享:Golang並發編程
Goroutine
在Go語言中,語言本身就已經實現和支持了並發, 我們只需要通過go關鍵字來開啟goroutine即可。
gouroutine其實就是一種協程,類似其他語言中的coroutine, 是在編譯器或虛擬機層面上的多任務。它可以運行在一個或多個線程上,但不同於線程,它是非搶佔式的,所以協程很輕量。
上述代碼就開啟了1000個協程,在1ms內不斷的列印字元串,這裡需要注意兩個點:
time.Sleep
在main函數退出前,Sleep了1ms。這是因為當main函數退出時,之前開的協程也會隨著退出,如果不Sleep,則無法看到列印信息。
匿名函數將變數i作為參數賦值傳入。
如果不傳參,變數i也能被使用,但是是以引用的方式。而i在main函數中在不斷自增,導致在goroutine列印信息中,無法知道是第幾個協程列印的。從列印信息上看,跟開線程沒什麼區別,無非就是數量上不同。但是在操作系統層面,線程是搶佔式,而我們之前說協程是非搶佔式的,這怎麼會一樣呢?
出現上述問題的原因在於,在調用Printf的時候,進行了切換, goroutine主動讓出了控制權。我們修改代碼如下,演示下非搶佔:
運行上述代碼,出現了死循環。因為在開闢的第一個goroutine中,一直循環執行a[ii] ,一直沒有讓出控制權;而main本質上也是個goroutine,所以後面的代碼都沒有執行完,也沒有退出。
遇到這種情況,我們可以在goroutine中主動讓出控制權,例如:
goroutine 可能會切換的點 (不能保證):
I/O,select
channel
等待鎖
runtime.Gosched()CSP並發模型
Go實現了兩種並發形式:
共享內存 鎖同步
CSP. 通過goroutine和channel來實現的.CSP並發模型是在1970年左右提出的概念,屬於比較新的概念,不同於傳統的多線程通過共享內存來通信,CSP講究的是「以通信的方式來共享內存」
channel
channel 是用來在不同goroutine之間進行通信的,無論傳值還是取值, 它都是阻塞的。
上面代碼直接運行會造成死鎖:
所以一般在使用channel前先開一個goroutine去接收channel:
在上述代碼中,我們定義了一個createWorker,用來創建一個接收者,同時返回了一個channel。同時我們可以對返回的channel做限制,例如:
一般可以通過n := - c來接收數據,在上述例子中使用了range,因為channel是可以close的。
close(c)關閉channel, 但是關閉後在worker中依然能接收到channel(只要goroutine沒有退出)。而接收到的數據是定義的channel的零值,在上述例子中,則收到0.
通過n,ok := - c的ok來判斷channel是否關閉;也可以通過range來接收;
如果往已經關閉的channel寫數據,會panic:send on closed channel.不要從接收端關閉channel,也不要關閉有多個並發發送者的channel等待任務結束
在之前的例子中,我們都是通過Sleep方法來粗略的控制任務的執行,這在實際生產中肯定不能這麼干。之前也說了channel是用來通信的,那麼我們可以通過channel來告訴使用者任務已經執行完了。 代碼優化如下:
除了我們自己定義channel,go也為我們提供了sync.WaitGroup,來管理一組任務。
將struct中的done抽象成一個方法,在create的時候實現,這樣在worker中就不用管具體代碼了,只要調用done方法即可。
※Kotlin:命名參數、默認參數值、可變參數、局部函數
※Kotlin技術分享:擴展函數和擴展屬性
TAG:千鋒JAVA開發學院 |