Go從語言層面就支援了并行,這讓C/C++程式猿們淚流滿面

一、goroutine
goroutine是Go語言并行設計的核心。goroutine說到底就是線程,但它比線程更小,十幾個goroutine可能展現在底層就是五六個線程,Go語言内部幫你實作了這些goroutine之間的記憶體共享。執行goroutine隻需極少的棧記憶體(大概是4~5KB),當然會根據相應的資料伸縮。是以,可同時運作成千上萬個并發任務。goroutine比thread更易用、更高效、更輕便。
goroutine是通過Go語言runtime管理的一個線程管理器。goroutine通過go關鍵字實作,其實就是一個普通的函數,類似于線程函數:
go hello(a, b, c)
通過關鍵字go就啟動了一個goroutine,舉例說明如下:
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i :=0; i
fmt.Println(s)
}
}
func main() {
go say("world") //開一個新的goroutines執行
say("hello") //目前goroutines執行
輸出:
hello
world
上面多個goroutine運作在同一個程序裡面,共享記憶體資料,不過設計上應該遵循:不要通過共享來通信,而要通過通信來共享。
runtime.Gosched()表示讓CPU把時間片讓給别人,下次某個時候繼續恢複執行該goroutine。
預設情況下,排程器僅适用單線程,也就是說隻實作了并發。想要發揮多核處理器的并行,需要在程式中顯示調用runtime.GOMAXPROCS(n)告訴排程器同時使用多個線程。GOMAXPROCS設定了同時運作邏輯代碼的系統線程的最大數量,并傳回之前的設定。如果n這篇文章。
二、channels
goroutine運作在相同的位址空間,是以,通路共享記憶體必須做好同步。Go語言提供了很好的通信機制channel。channel可以與Unix shell中的雙向管道做類比,通過它發送或者接收值。這些值隻能是特定的類型:channel類型。定義channel時,也需要定義發送到channel的值的類型。注意:必須使用make建立channel。
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
channel通過操作符
ch
v :=
我們把這些應用到我們的例子中來:
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v:=range a {
sum += v
c
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y :=
fmt.Println(x, y, x+y)
預設情況下,channel接收和發送資料的都是阻塞的,除非另一端已經準備好,這樣就使得goroutines同步變得更加簡單,而不需要顯示的lock。所謂阻塞,就是如果讀取(value :=
三、Buffered channels
前面介紹了預設的非緩存類型的channel,不過Go語言也允許指定channel的緩沖大小,很簡單,就是channel可以存儲多少元素。ch:=make(chan bool, 4),建立了可以存儲4個元素的bool型channel。在這個channel中,前4個元素可以無阻塞的寫入,當寫入第5個元素時,代碼将會阻塞,直到其它goroutine從channel中讀取一些元素,騰出空間。
ch := make(chan type, value)
value == 0 !無緩沖(阻塞)
value > 0 !緩沖(非阻塞,直到value個元素)
舉例說明如下(修改相應的value值):
c := make(chan int, 2) //修改2為1就報錯,修改2為3可以正常運作
fmt.Println(
四、Range和Close
前面例子中,需要讀取兩次c,不是很友善,也可以通過range,像操作slice或者map一樣操作緩存類型的channel。具體請看下例:
package main
import (
"fmt"
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i:+0; i
c
x, y = y, x+y
close(c)
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
for i := range c能夠不斷讀取channel裡面的資料,直到該channel被顯示關閉。從上面代碼可以看出,生産者通過關鍵字close函數顯示關閉channel。關閉channel後就無法再發送任何資料了,消費者可以通過文法v, ok :=
需要注意的是:應該在生産者的地方關閉channel,而不是消費者的地方去關閉它,這樣容易引起panic。
另外,channel不像檔案之類需要經常去關閉,隻有當你确實沒有任何資料發送了,或者想顯式的結束range循環之類的操作。
五、Select
前文介紹的都是隻有一個channel的情況,如果有多個channel,可以通過關鍵字select來監聽channel上的資料流動。
select預設是阻塞的,隻有當監聽的channel中發送或接收可以進行時才會運作,當多個channel都準備好的時候,select是随機選擇一個執行的。
import "fmt"
func fibonacci(c, quit chan int) {
for {
select {
case c
x, y = y, x+y
case
fmt.Println("quit")
return
}
c := make(chan int)
quit := make(chan int)
go func() {
for i:=0; i
fmt.Println(
quit
} ()
fibonacci(c, quit)
在select裡面還有default文法,這類似于switch,default就是當監聽的channel都沒有準備好的時候,預設執行的(select不再阻塞等待channel)。
select {
case i :=
//use i
default:
//當c阻塞的時候執行這裡
六、逾時
有時候會出現goroutine阻塞的情況,可以利用select設定逾時來避免整個程式進入阻塞狀态,具體通過如下方式實作:
c := make(chan bool)
for {
select {
case v :=
println(v)
case
println("timeout")
o
break
}
}()
七、runtime goroutine
runtime包中有幾個處理goroutine的函數。
(1)Goexit
退出目前執行的goroutine,但是defer函數還會繼續調用。
(2)Gosched
讓出目前goroutine的執行權限,排程器安排其它等待的任務運作,并在下次某個時候從該位置恢複執行。
(3)NumCPU
傳回CPU核數量。
(4)NumGoroutine
傳回正在執行和排隊的任務總數。
(5)GOMAXPROCS
用來設定可以運作的CPU核數。