goroutine
并行(parallel):指在同一時刻,有多條指令在多個處理器上同時執(zhí)行。
并發(fā)(concurrency):指在同一時刻只能有一條指令執(zhí)行,但多個進程指令被快速的輪換執(zhí)行,使得在宏觀上具有多個進程同時執(zhí)行的效果,但在微觀上并不是同時執(zhí)行的,只是把時間分成若干段,使多個進程快速交替的執(zhí)行。
有人把Go比作21世紀的C語言,第一是因為Go語言設計簡單,第二,21世紀最重要的就是并行程序設計,而Go從語言層面就支持了并行。同時,并發(fā)程序的內(nèi)存管理有時候是非常復雜的,而Go語言提供了自動垃圾回收機制。
Go語言為并發(fā)編程而內(nèi)置的上層API基于CSP(communicating sequential processes, 順序通信進程)模型。這就意味著顯式鎖都是可以避免的,因為Go語言通過相冊安全的通道發(fā)送和接受數(shù)據(jù)以實現(xiàn)同步,這大大地簡化了并發(fā)程序的編寫。
一般情況下,一個普通的桌面計算機跑十幾二十個線程就有點負載過大了,但是同樣這臺機器卻可以輕松地讓成百上千甚至過萬個goroutine進行資源競爭。
goroutine是Go并行設計的核心。goroutine說到底其實就是協(xié)程,但是它比線程更小,十幾個goroutine可能體現(xiàn)在底層就是五六個線程,Go語言內(nèi)部幫你實現(xiàn)了這些goroutine之間的內(nèi)存共享。執(zhí)行g(shù)oroutine只需極少的棧內(nèi)存(大概是4~5KB),當然會根據(jù)相應的數(shù)據(jù)伸縮。也正因為如此,可同時運行成千上萬個并發(fā)任務。goroutine比thread更易用、更高效、更輕便。
只需在函數(shù)調(diào)?語句前添加 go 關(guān)鍵字,就可創(chuàng)建并發(fā)執(zhí)?單元。開發(fā)?員無需了解任何執(zhí)?細節(jié),調(diào)度器會自動將其安排到合適的系統(tǒng)線程上執(zhí)行。
在并發(fā)編程里,我們通常想講一個過程切分成幾塊,然后讓每個goroutine各自負責一塊工作。當一個程序啟動時,其主函數(shù)即在一個單獨的goroutine中運行,我們叫它main goroutine。新的goroutine會用go語句來創(chuàng)建。
package main
import (
"fmt"
"time"
)
func newTask() {
for {
fmt.Println("this is newTask thread")
time.Sleep(time.Second)
}
}
func main() {
fmt.Println("并發(fā)編程演示案例")
// 1. 創(chuàng)建goroutine
go newTask()
for {
fmt.Println("this is main goroutine thread")
time.Sleep(time.Second)
}
}
主goroutine退出后,其它的工作goroutine也會自動退出,因此可能會導致:主協(xié)程先退出導致子協(xié)程沒來得及調(diào)用
package main
import (
"fmt"
"time"
)
func newTask() {
for {
fmt.Println("this is newTask thread")
time.Sleep(time.Second)
}
}
func main() {
fmt.Println("并發(fā)編程演示案例")
// 1. 主goroutine退出后,其它的工作goroutine也會自動退出,因此可能會導致:主協(xié)程先退出導致子協(xié)程沒來得及調(diào)用
go newTask()
var i int = 0
for {
if i < 5 {
fmt.Println("this is main goroutine thread ", i)
time.Sleep(time.Second)
i++
} else {
break
}
}
}
控制臺輸出:
并發(fā)編程演示案例
this is main goroutine thread 0
this is newTask thread
this is main goroutine thread 1
this is newTask thread
this is main goroutine thread 2
this is newTask thread
this is newTask thread
this is main goroutine thread 3
this is newTask thread
this is main goroutine thread 4
runtime.Gosched() 用于讓出CPU時間片,讓出當前goroutine的執(zhí)行權(quán)限,調(diào)度器安排其他等待的任務運行,并在下次某個時候從該位置恢復執(zhí)行。
這就像跑接力賽,A跑了一會碰到代碼runtime.Gosched() 就把接力棒交給B了,A歇著了,B繼續(xù)跑。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("runtime.Gosched的使用演示案例")
// 1. runtime.Gosched的使用
go func() {
for i := 0; i < 5; i++ {
fmt.Println("this is newTask goroutine ", i)
}
}()
for i := 0; i < 2; i++ {
runtime.Gosched()
fmt.Println("this is main goroutine ", i)
}
// 輸出內(nèi)容:
// this is newTask goroutine 0
// this is newTask goroutine 1
// this is newTask goroutine 2
// this is newTask goroutine 3
// this is newTask goroutine 4
// this is main goroutine 0
// this is main goroutine 1
}
調(diào)用 runtime.Goexit() 將立即終止當前 goroutine 執(zhí)?,調(diào)度器確保所有已注冊 defer延遲調(diào)用被執(zhí)行。
package main
import (
"fmt"
"runtime"
)
func test() {
fmt.Println("dddddddddddd")
runtime.Goexit() // 終止所在協(xié)程
fmt.Println("eeeeeeeeeeee")
}
func main() {
fmt.Println("runtime.Gosched的使用演示案例")
// 1. runtime.Goexit的使用
//調(diào)用 runtime.Goexit() 將立即終止當前 goroutine 執(zhí)?,調(diào)度器確保所有已注冊 defer延遲調(diào)用被執(zhí)行。
go func() {
fmt.Println("aaaaaaaaaaaa")
test()
fmt.Println("bbbbbbbbbbbb")
}()
for { //死循環(huán),目的是不讓主協(xié)程結(jié)束
}
// 輸出內(nèi)容:
// aaaaaaaaaaaa
// dddddddddddd
}
調(diào)用 runtime.GOMAXPROCS() 用來設置可以并行計算的CPU核數(shù)的最大值,并返回之前的值。
package main
import (
"fmt"
"runtime"
)
func main() {
fmt.Println("untime.GOMAXPROCS的使用演示案例")
max := runtime.GOMAXPROCS(4) //調(diào)用 runtime.GOMAXPROCS() 用來設置可以并行計算的CPU核數(shù)的最大值,并返回之前的值。
fmt.Println("max: ", max) //max: 4
for {
go fmt.Print(1)
fmt.Print(0)
}
// 輸出內(nèi)容:如果gomaxprocs設置為1則,
// 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// 0011111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 輸出內(nèi)容:如果gomaxprocs設置為4則,
// 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
// 0000000000000000000000000000000111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
// 1111111111111000000000000000000000000000000000000000000000000000000000000000000000000000
// 0000000000000000000000000000000000000000111111111111111111111111111111111111111111111111
}
多任務資源競爭問題:打印機問題
package main
import (
"fmt"
"time"
)
func HpPrinter(str string) { // 惠普打印機
for _, c := range str {
time.Sleep(time.Second)
fmt.Printf("%c", c)
}
}
func main() {
go HpPrinter("hello") // 小明去打印hello
go HpPrinter("ABC") //小麗打印ABC
for {
}
// 輸出結(jié)果:并不是自己想要的結(jié)果
//AheBCllo
//hABelClo
}
為了解決這個問題,就引出了channel
channel
goroutine運行在相同的地址空間,因此訪問共享內(nèi)存必須做好同步。goroutine 奉行通過通信來共享內(nèi)存,而不是共享內(nèi)存來通信。
引?類型 channel 是 CSP 模式的具體實現(xiàn),用于多個 goroutine 通訊。其內(nèi)部實現(xiàn)了同步,確保并發(fā)安全。
和map類似,channel也一個對應make創(chuàng)建的底層數(shù)據(jù)結(jié)構(gòu)的引用。
當我們復制一個channel或用于函數(shù)參數(shù)傳遞時,我們只是拷貝了一個channel引用,因此調(diào)用者何被調(diào)用者將引用同一個channel對象。和其它的引用類型一樣,channel的零值也是nil。
定義一個channel時,也需要定義發(fā)送到channel的值的類型。channel可以使用內(nèi)置的make()函數(shù)來創(chuàng)建:
make(chan Type) //等價于make(chan Type, 0)
make(chan Type, capacity)
當 capacity= 0 時,channel 是無緩沖阻塞讀寫的,當capacity> 0 時,channel 有緩沖、是非阻塞的,直到寫滿 capacity個元素才阻塞寫入。
channel通過操作符<-來接收和發(fā)送數(shù)據(jù),發(fā)送和接收數(shù)據(jù)語法:
channel <- value //發(fā)送value到channel
<-channel //接收并將其丟棄
x := <-channel //從channel中接收數(shù)據(jù),并賦值給x
x, ok := <-channel //功能同上,同時檢查通道是否已關(guān)閉或者是否為空
默認情況下,channel接收和發(fā)送數(shù)據(jù)都是阻塞的,除非另一端已經(jīng)準備好,這樣就使得goroutine同步變的更加的簡單,而不需要顯式的lock。
package main
import (
"fmt"
)
func main() {
ch := make(chan string)
defer fmt.Println("主線程調(diào)用結(jié)束")
go func() {
defer fmt.Println("子協(xié)程調(diào)用結(jié)束")
for i := 0; i < 5; i++ {
fmt.Println("子協(xié)程調(diào)用中 ", i)
}
ch <- "success"
}()
data := <-ch //沒有數(shù)據(jù)前,阻塞
fmt.Println("主線程取得數(shù)據(jù):", data)
// 輸出結(jié)果:
// 子協(xié)程調(diào)用中 0
// 子協(xié)程調(diào)用中 1
// 子協(xié)程調(diào)用中 2
// 子協(xié)程調(diào)用中 3
// 子協(xié)程調(diào)用中 4
// 子協(xié)程調(diào)用結(jié)束
// 主線程取得數(shù)據(jù): success
// 主線程調(diào)用結(jié)束
}
無緩沖channel
無緩沖的通道(unbuffered channel)是指在接收前沒有能力保存任何值的通道。
這種類型的通道要求發(fā)送 goroutine 和接收 goroutine 同時準備好,才能完成發(fā)送和接收操作。如果兩個goroutine沒有同時準備好,通道會導致先執(zhí)行發(fā)送或接收操作的 goroutine 阻塞等待。
這種對通道進行發(fā)送和接收的交互行為本身就是同步的。其中任意一個操作都無法離開另一個操作單獨存在。
下圖展示兩個 goroutine 如何利用無緩沖的通道來共享一個值:

l 在第 1 步,兩個 goroutine 都到達通道,但哪個都沒有開始執(zhí)行發(fā)送或者接收。
l 在第 2 步,左側(cè)的 goroutine 將它的手伸進了通道,這模擬了向通道發(fā)送數(shù)據(jù)的行為。這時,這個 goroutine 會在通道中被鎖住,直到交換完成。
l 在第 3 步,右側(cè)的 goroutine 將它的手放入通道,這模擬了從通道里接收數(shù)據(jù)。這個 goroutine 一樣也會在通道中被鎖住,直到交換完成。
l 在第 4 步和第 5 步,進行交換,并最終,在第 6 步,兩個 goroutine 都將它們的手從通道里拿出來,這模擬了被鎖住的 goroutine 得到釋放。兩個 goroutine 現(xiàn)在都可以去做別的事情了。
無緩沖的channel創(chuàng)建格式:
make(chan Type) //等價于make(chan Type, 0)
如果沒有指定緩沖區(qū)容量,那么該通道就是同步的,因此會阻塞到發(fā)送者準備好發(fā)送和接收者準備好接收。
package main
import (
"fmt"
//"time"
)
func main() {
ch := make(chan int, 0) //創(chuàng)建一個無緩存的channel
//fmt.Printf("len(ch)=%d,cap(ch)=%d\n", len(ch), cap(ch)) //len(ch)=0,cap(ch)=0
go func() {
for i := 0; i < 3; i++ {
fmt.Println("子協(xié)程:i =", i)
data := <-ch //讀管道中的內(nèi)容,如果沒有前,阻塞
fmt.Println("子協(xié)程取得數(shù)據(jù):data =", data)
}
}()
//time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ {
fmt.Println("main協(xié)程:i =", i)
// 往chan里面寫數(shù)據(jù)
fmt.Println("main協(xié)程發(fā)送數(shù)據(jù):data =", i)
ch <- i
}
// 輸出結(jié)果:
// main協(xié)程:i = 0
// main協(xié)程發(fā)送數(shù)據(jù):data = 0
// 子協(xié)程:i = 0
// 子協(xié)程取得數(shù)據(jù):data = 0
// 子協(xié)程:i = 1
// main協(xié)程:i = 1
// main協(xié)程發(fā)送數(shù)據(jù):data = 1
// main協(xié)程:i = 2
// main協(xié)程發(fā)送數(shù)據(jù):data = 2
// 子協(xié)程取得數(shù)據(jù):data = 1
// 子協(xié)程:i = 2
// 子協(xié)程取得數(shù)據(jù):data = 2
}
有緩沖channel
有緩沖的通道(buffered channel)是一種在被接收前能存儲一個或者多個值的通道。
這種類型的通道并不強制要求 goroutine 之間必須同時完成發(fā)送和接收。通道會阻塞發(fā)送和接收動作的條件也會不同。只有在通道中沒有要接收的值時,接收動作才會阻塞。只有在通道沒有可用緩沖區(qū)容納被發(fā)送的值時,發(fā)送動作才會阻塞。
這導致有緩沖的通道和無緩沖的通道之間的一個很大的不同:無緩沖的通道保證進行發(fā)送和接收的 goroutine 會在同一時間進行數(shù)據(jù)交換;有緩沖的通道沒有這種保證。
示例圖如下:

l 在第 1 步,右側(cè)的 goroutine 正在從通道接收一個值。
l 在第 2 步,右側(cè)的這個 goroutine獨立完成了接收值的動作,而左側(cè)的 goroutine 正在發(fā)送一個新值到通道里。
l 在第 3 步,左側(cè)的goroutine 還在向通道發(fā)送新值,而右側(cè)的 goroutine 正在從通道接收另外一個值。這個步驟里的兩個操作既不是同步的,也不會互相阻塞。
l 最后,在第 4 步,所有的發(fā)送和接收都完成,而通道里還有幾個值,也有一些空間可以存更多的值。
有緩沖的channel創(chuàng)建格式:
make(chan Type, capacity)
如果給定了一個緩沖區(qū)容量,通道就是異步的。只要緩沖區(qū)有未使用空間用于發(fā)送數(shù)據(jù),或還包含可以接收的數(shù)據(jù),那么其通信就會無阻塞地進行。
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int, 3) //創(chuàng)建一個有緩存的channel
fmt.Printf("len(ch)=%d,cap(ch)=%d\n", len(ch), cap(ch)) //len(ch)=0,cap(ch)=3
go func() {
for i := 0; i < 10; i++ {
ch <- i
fmt.Printf("子協(xié)程[%d],len(ch)=%d,cap(ch)=%d\n", i, len(ch), cap(ch))
}
}()
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ {
num := <-ch
fmt.Println("num = ", num)
}
// 輸出結(jié)果:
// 子協(xié)程[0],len(ch)=1,cap(ch)=3
// 子協(xié)程[1],len(ch)=2,cap(ch)=3
// 子協(xié)程[2],len(ch)=3,cap(ch)=3
// num = 0
// num = 1
// num = 2
// num = 3
// 子協(xié)程[3],len(ch)=3,cap(ch)=3
// 子協(xié)程[4],len(ch)=0,cap(ch)=3
// 子協(xié)程[5],len(ch)=1,cap(ch)=3
// 子協(xié)程[6],len(ch)=2,cap(ch)=3
// 子協(xié)程[7],len(ch)=3,cap(ch)=3
// num = 4
// num = 5
// num = 6
// num = 7
// num = 8
// 子協(xié)程[8],len(ch)=3,cap(ch)=3
// 子協(xié)程[9],len(ch)=0,cap(ch)=3
// num = 9
}
關(guān)閉channel:
如果發(fā)送者知道,沒有更多的值需要發(fā)送到channel的話,那么讓接收者也能及時知道沒有多余的值可接收將是有用的,因為接收者可以停止不必要的接收等待。這可以通過內(nèi)置的close函數(shù)來關(guān)閉channel實現(xiàn)。
注意點:
l channel不像文件一樣需要經(jīng)常去關(guān)閉,只有當你確實沒有任何發(fā)送數(shù)據(jù)了,或者你想顯式的結(jié)束range循環(huán)之類的,才去關(guān)閉channel;
l 關(guān)閉channel后,無法向channel 再發(fā)送數(shù)據(jù)(引發(fā) panic 錯誤后導致接收立即返回零值);
l 關(guān)閉channel后,可以繼續(xù)向channel接收數(shù)據(jù);
l 對于nil channel,無論收發(fā)都會被阻塞。
package main
import (
"fmt"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
ch <- i //往chan里面寫數(shù)據(jù)
if i == 5 {
close(ch)
//ch <- 10,err 關(guān)閉通道就不能寫入東西了
break
}
}
}()
for {
if num, ok := <-ch; ok == true {
fmt.Println("num = ", num)
} else {
fmt.Println("通道被關(guān)閉了,程序結(jié)束")
break
}
}
// 輸出結(jié)果:
// num = 0
// num = 1
// num = 2
// num = 3
// num = 4
// num = 5
// 通道被關(guān)閉了,程序結(jié)束
}
通過range遍歷channel內(nèi)容
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println("往chan里面寫數(shù)據(jù)")
ch <- i //往chan里面寫數(shù)據(jù)
if i == 5 {
close(ch)
//ch <- 10,err 關(guān)閉通道就不能寫入東西了
break
}
time.Sleep(time.Second)
}
}()
for num := range ch {
fmt.Println("遍歷channel:", num)
}
// 輸出結(jié)果:
// 往chan里面寫數(shù)據(jù)
// 遍歷channel: 0
// 往chan里面寫數(shù)據(jù)
// 遍歷channel: 1
// 往chan里面寫數(shù)據(jù)
// 遍歷channel: 2
// 往chan里面寫數(shù)據(jù)
// 遍歷channel: 3
// 往chan里面寫數(shù)據(jù)
// 遍歷channel: 4
// 往chan里面寫數(shù)據(jù)
// 遍歷channel: 5
}
單向channel特點
默認情況下,通道是雙向的,也就是,既可以往里面發(fā)送數(shù)據(jù)也可以同里面接收數(shù)據(jù)。
但是,我們經(jīng)常見一個通道作為參數(shù)進行傳遞而值希望對方是單向使用的,要么只讓它發(fā)送數(shù)據(jù),要么只讓它接收數(shù)據(jù),這時候我們可以指定通道的方向。
單向channel變量的聲明非常簡單,如下:
var ch1 chan int // ch1是一個正常的channel,不是單向的
var ch2 chan<- float64 // ch2是單向channel,只用于寫float64數(shù)據(jù)
var ch3 <-chan int // ch3是單向channel,只用于讀取int數(shù)據(jù)
l chan<- 表示數(shù)據(jù)進入管道,要把數(shù)據(jù)寫進管道,對于調(diào)用者就是輸出。
l <-chan 表示數(shù)據(jù)從管道出來,對于調(diào)用者就是得到管道的數(shù)據(jù),當然就是輸入。
可以將 channel 隱式轉(zhuǎn)換為單向隊列,只收或只發(fā),不能將單向 channel 轉(zhuǎn)換為普通 channel:
package main
import (
"fmt"
)
func main() {
ch := make(chan int) //定義一個channel,默認是雙向的
var writeCh chan<- int = ch // 只能寫,不能讀
var readCh <-chan int = ch //只能讀,不能寫
writeCh <- 666 //ok
//n := <-writeCh //err, invalid operation: <-writeCh (receive from send-only type chan<- int)
num := <-readCh
// readCh <- 777 //err,invalid operation: readCh <- 777 (send to receive-only type <-chan int)
fmt.Println(num)
}
單向channel的應用:
package main
import (
"fmt"
"time"
)
// 生產(chǎn)者,寫能寫不能讀
func producer(out chan<- int) {
for i := 0; i < 5; i++ {
fmt.Println("生產(chǎn)者producer:", (i * i))
out <- i * i
time.Sleep(time.Second)
}
close(out)
}
// 消費者,寫能讀不能寫
func consumer(in <-chan int) {
for num := range in {
fmt.Println("消費者consumer:", num)
}
}
func main() {
ch := make(chan int) //定義一個channel,默認是雙向的
go producer(ch)
consumer(ch)
fmt.Println("程序結(jié)束")
// 輸出結(jié)果:
// 生產(chǎn)者producer: 0
// 消費者consumer: 0
// 生產(chǎn)者producer: 1
// 消費者consumer: 1
// 生產(chǎn)者producer: 4
// 消費者consumer: 4
// 生產(chǎn)者producer: 9
// 消費者consumer: 9
// 生產(chǎn)者producer: 16
// 消費者consumer: 16
// 程序結(jié)束
}
Timer
Timer是一個定時器,代表未來的一個單一事件,你可以告訴timer你要等待多長時間,它提供一個channel,在將來的那個時間那個channel提供了一個時間值。
Timer實現(xiàn)延時功能、定時器停止、定時器重置
package main
import (
"fmt"
"time"
)
func main() {
// 1. Timer的使用
fmt.Println("------Timer的使用---------")
fmt.Println("Timer的使用演示案例")
t1 := time.NewTimer(2 * time.Second)
fmt.Println("當前時間:", time.Now()) // 2019-03-20 14:00:41.9098831 +0800 CST m=+0.001982001
t2 := <-t1.C
fmt.Println("t2:", t2) //2019-03-20 14:00:43.9099262 +0800 CST m=+2.002025101
fmt.Println("------Timer實現(xiàn)延時功能---------")
// 2. Timer實現(xiàn)延時功能
// 定時5秒
t3 := time.NewTimer(5 * time.Second)
fmt.Println("5s倒計時開始")
<-t3.C
fmt.Println("5s時間到")
// 3. 定時器停止
fmt.Println("------定時器停止---------")
t4 := time.NewTimer(3 * time.Second)
fmt.Println("3s后執(zhí)行子協(xié)程")
go func() {
<-t4.C
fmt.Println("時間到,子協(xié)程打印")
}()
t4.Stop() // 如果加了這句話,則子協(xié)程的任務就會取消,也就是不會打印
// 4. 定時器重置
fmt.Println("------定時器重置---------")
fmt.Println("定時器重置5s->1s")
t5 := time.NewTimer(5 * time.Second)
t5.Reset(1 * time.Second) //重新設置為1s
<-t5.C
fmt.Println("定時器重置success")
for {
}
}
Ticker
package main
import (
"fmt"
"time"
)
func main() {
// 1. Ticker的使用
fmt.Println("Ticker的使用演示案例")
t1 := time.NewTicker(1 * time.Second)
i := 0
for {
<-t1.C
fmt.Println("ticker:", i)
if i > 10 {
break
} else {
i++
}
}
// 輸出結(jié)果:
// Ticker的使用演示案例
// ticker: 0
// ticker: 1
// ticker: 2
// ticker: 3
// ticker: 4
// ticker: 5
// ticker: 6
// ticker: 7
// ticker: 8
// ticker: 9
// ticker: 10
// ticker: 11
}
Select
Go里面提供了一個關(guān)鍵字select,通過select可以監(jiān)聽channel上的數(shù)據(jù)流動。
select的用法與switch語言非常類似,由select開始一個新的選擇塊,每個選擇條件由case語句來描述。
與switch語句可以選擇任何可使用相等比較的條件相比, select有比較多的限制,其中最大的一條限制就是每個case語句里必須是一個IO操作,大致的結(jié)構(gòu)如下:
select {
case <-chan1:
// 如果chan1成功讀到數(shù)據(jù),則進行該case處理語句
case chan2 <- 1:
// 如果成功向chan2寫入數(shù)據(jù),則進行該case處理語句
default:
// 如果上面都沒有成功,則進入default處理流程
}
在一個select語句中,Go語言會按順序從頭至尾評估每一個發(fā)送和接收的語句。
如果其中的任意一語句可以繼續(xù)執(zhí)行(即沒有被阻塞),那么就從那些可以執(zhí)行的語句中任意選擇一條來使用。
如果沒有任意一條語句可以執(zhí)行(即所有的通道都被阻塞),那么有兩種可能的情況:
l 如果給出了default語句,那么就會執(zhí)行default語句,同時程序的執(zhí)行會從select語句后的語句中恢復。
l 如果沒有default語句,那么select語句將被阻塞,直到至少有一個通信可以進行下去。
通過select實現(xiàn)斐波那契數(shù)列
package main
import (
"fmt"
)
func fibonacci(ch chan<- int, quit <-chan bool) {
x, y := 1, 1
for {
select {
case ch <- x:
x, y = y, x+y
case flag := <-quit:
fmt.Println("quit:", flag)
return
}
}
}
func main() {
// 1.通過select實現(xiàn)斐波那契數(shù)列 1、1、2、3、5、8、13、21、34、……
fmt.Println("select的使用演示案例")
ch := make(chan int)
quit := make(chan bool)
go func() {
for i := 0; i < 8; i++ {
num := <-ch
fmt.Println("num = ", num)
}
quit <- true
}()
fibonacci(ch, quit)
// 輸出結(jié)果:
// select的使用演示案例
// num = 1
// num = 1
// num = 2
// num = 3
// num = 5
// num = 8
// num = 13
// num = 21
// quit: true
}
select實現(xiàn)的超時機制
有時候會出現(xiàn)goroutine阻塞的情況,那么我們?nèi)绾伪苊庹麄€程序進入阻塞的情況呢?我們可以利用select來設置超時,通過如下的方式實現(xiàn):
package main
import (
"fmt"
"time"
)
func main() {
// 1. select實現(xiàn)的超時機制
fmt.Println("select實現(xiàn)的超時機制演示案例")
ch := make(chan int)
quit := make(chan bool)
go func() {
for {
select {
case num := <-ch:
fmt.Println("程序執(zhí)行中", num)
case <-time.After(3 * time.Second):
fmt.Println("程序超時")
quit <- true
}
}
}()
for i := 0; i < 10; i++ {
ch <- i
time.Sleep(time.Second)
}
<-quit
fmt.Println("程序結(jié)束")
// 輸出結(jié)果:
// select實現(xiàn)的超時機制演示案例
// 程序執(zhí)行中 0
// 程序執(zhí)行中 1
// 程序執(zhí)行中 2
// 程序執(zhí)行中 3
// 程序執(zhí)行中 4
// 程序執(zhí)行中 5
// 程序執(zhí)行中 6
// 程序執(zhí)行中 7
// 程序執(zhí)行中 8
// 程序執(zhí)行中 9
// 程序超時
// 程序結(jié)束
}
END