# select
> golang 的 select 就是監聽 IO 操作,當 IO 操作發生時,觸發相應的動作。
在執行select語句的時候,運行時系統會自上而下地判斷每個case中的發送或接收操作是否可以被立即執行【立即執行:意思是當前Goroutine不會因此操作而被阻塞,還需要依據通道的具體特性(緩存或非緩存)】
- **每個case語句里必須是一個IO操作**
- **所有channel表達式都會被求值、所有被發送的表達式都會被求值**
- **如果任意某個case可以進行,它就執行(其他被忽略)**。
- **如果有多個case都可以運行,Select會隨機公平地選出一個執行(其他不會執行)**。
- **如果有default子句,case不滿足條件時執行該語句。**
- **如果沒有default字句,select將阻塞,直到某個case可以運行;Go不會重新對channel或值進行求值。**
### select 語句用法
注意到 select 的代碼形式和 switch 非常相似, 不過 select 的 case 里的操作語句只能是【IO 操作】 。
此示例里面 select 會一直等待等到某個 case 語句完成, 也就是等到成功從 ch1 或者 ch2 中讀到數據,如果都不滿足條件且存在default case, 那么default case會被執行。 則 select 語句結束。
示例:
~~~
package main
import (
"fmt"
)
func main(){
ch1 := make(chan int, 1)
ch2 := make(chan int, 1)
select {
case e1 := <-ch1:
//如果ch1通道成功讀取數據,則執行該case處理語句
fmt.Printf("1th case is selected. e1=%v",e1)
case e2 := <-ch2:
//如果ch2通道成功讀取數據,則執行該case處理語句
fmt.Printf("2th case is selected. e2=%v",e2)
default:
//如果上面case都沒有成功,則進入default處理流程
fmt.Println("default!.")
}
}
~~~
### select分支選擇規則
所有跟在case關鍵字右邊的發送語句或接收語句中的通道表達式和元素表達式都會先被求值。無論它們所在的case是否有可能被選擇都會這樣。
> 求值順序:自上而下、從左到右
示例:
~~~
package main
import (
"fmt"
)
//定義幾個變量,其中chs和numbers分別代表了包含了有限元素的通道列表和整數列表
var ch1 chan int
var ch2 chan int
var chs = []chan int{ch1, ch2}
var numbers = []int{1,2,3,4,5}
func main(){
select {
case getChan(0) <- getNumber(2):
fmt.Println("1th case is selected.")
case getChan(1) <- getNumber(3):
fmt.Println("2th case is selected.")
default:
fmt.Println("default!.")
}
}
func getNumber(i int) int {
fmt.Printf("numbers[%d]\n", i)
return numbers[i]
}
func getChan(i int) chan int {
fmt.Printf("chs[%d]\n", i)
return chs[i]
}
~~~
輸出:
> chs[0]
numbers[2]
chs[1]
numbers[3]
default!.
可以看出求值順序。滿足自上而下、自左而右這條規則。
### 隨機執行case
如果同時有多個case滿足條件,通過一個偽隨機的算法決定哪一個case將會被執行。
示例:
~~~
package main
import (
"fmt"
)
func main(){
chanCap := 5
ch7 := make(chan int, chanCap)
for i := 0; i < chanCap; i++ {
select {
case ch7 <- 1:
case ch7 <- 2:
case ch7 <- 3:
}
}
for i := 0; i < chanCap; i++ {
fmt.Printf("%v\n", <-ch7)
}
}
~~~
輸出:(注:每次運行都會不一樣)
3
3
2
3
1
### 一些慣用手法示例
示例一:單獨啟用一個Goroutine執行select,等待通道關閉后結束循環
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan int, 1)
//給ch11通道寫入數據
for i := 0; i < 1000; i++ {
ch11 <- i
}
//關閉ch11通道
close(ch11)
//單獨起一個Goroutine執行select
go func(){
var e int
ok := true
for{
select {
case e,ok = <- ch11:
if !ok {
fmt.Println("End.")
break
}
fmt.Printf("ch11 -> %d\n",e)
}
//通道關閉后退出for循環
if !ok {
sign <- 0
break
}
}
}()
//慣用手法,讀取sign通道數據,為了等待select的Goroutine執行。
<- sign
}
~~~
> ch11 -> 0
ch11 -> 1
…
ch11 -> 999
End.
示例二:加以改進,我們不想等到通道被關閉后再退出循環,利用一個輔助通道模擬出操作超時。
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan int, 1)
//給ch11通道寫入數據
for i := 0; i < 1000; i++ {
ch11 <- i
}
//關閉ch11通道
close(ch11)
//我們不想等到通道被關閉之后再推出循環,我們創建并初始化一個輔助的通道,利用它模擬出操作超時行為
timeout := make(chan bool,1)
go func(){
time.Sleep(time.Millisecond) //休息1ms
timeout <- false
}()
//單獨起一個Goroutine執行select
go func(){
var e int
ok := true
for{
select {
case e,ok = <- ch11:
if !ok {
fmt.Println("End.")
break
}
fmt.Printf("ch11 -> %d\n",e)
case ok = <- timeout:
//向timeout通道發送元素false后,該case幾乎馬上就會被執行, ok = false
fmt.Println("Timeout.")
break
}
//終止for循環
if !ok {
sign <- 0
break
}
}
}()
//慣用手法,讀取sign通道數據,為了等待select的Goroutine執行。
<- sign
}
~~~
ch11 -> 0
ch11 -> 1
…
ch11 -> 691
Timeout.
示例三:上面實現了單個操作的超時,但是那個超時觸發器開始計時有點早。
~~~
package main
import (
"fmt"
"time"
)
func main(){
//初始化通道
ch11 := make(chan int, 1000)
sign := make(chan int, 1)
//給ch11通道寫入數據
for i := 0; i < 1000; i++ {
ch11 <- i
}
//關閉ch11通道
//close(ch11),為了看效果先注釋掉
//單獨起一個Goroutine執行select
go func(){
var e int
ok := true
for{
select {
case e,ok = <- ch11:
if !ok {
fmt.Println("End.")
break
}
fmt.Printf("ch11 -> %d\n",e)
case ok = <- func() chan bool {
//經過大約1ms后,該接收語句會從timeout通道接收到一個新元素并賦值給ok,從而恰當地執行了針對單個操作的超時子流程,恰當地結束當前for循環
timeout := make(chan bool,1)
go func(){
time.Sleep(time.Millisecond)//休息1ms
timeout <- false
}()
return timeout
}():
fmt.Println("Timeout.")
break
}
//終止for循環
if !ok {
sign <- 0
break
}
}
}()
//慣用手法,讀取sign通道數據,為了等待select的Goroutine執行。
<- sign
}
~~~
ch11 -> 0
ch11 -> 1
…
ch11 -> 999
Timeout.
# 非緩沖的Channel
> 我們在初始化一個通道時將其容量設置成0,或者直接忽略對容量的設置,那么就稱之為非緩沖通道
~~~
ch1 := make(chan int, 1) //緩沖通道
ch2 := make(chan int, 0) //非緩沖通道
ch3 := make(chan int) //非緩沖通道
~~~
- 向此類通道發送元素值的操作會被阻塞,直到至少有一個針對該通道的接收操作開始進行為止。
- 從此類通道接收元素值的操作會被阻塞,直到至少有一個針對該通道的發送操作開始進行為止。
- 針對非緩沖通道的接收操作會在與之相應的發送操作完成之前完成。
對于第三條要特別注意,發送操作在向非緩沖通道發送元素值的時候,會等待能夠接收該元素值的那個接收操作。并且確保該元素值被成功接收,它才會真正的完成執行。而緩沖通道中,剛好相反,由于元素值的傳遞是異步的,所以發送操作在成功向通道發送元素值之后就會立即結束(它不會關心是否有接收操作)。
### 示例一
實現多個Goroutine之間的同步
~~~
package main
import (
"fmt"
"time"
)
func main(){
unbufChan := make(chan int)
//unbufChan := make(chan int, 1) 有緩沖容量
//啟用一個Goroutine接收元素值操作
go func(){
fmt.Println("Sleep a second...")
time.Sleep(time.Second)//休息1s
num := <- unbufChan //接收unbufChan通道元素值
fmt.Printf("Received a integer %d.\n", num)
}()
num := 1
fmt.Printf("Send integer %d...\n", num)
//發送元素值
unbufChan <- num
fmt.Println("Done.")
}
~~~
> 緩沖channel輸出結果如下:
Send integer 1…
Done.
======================
非緩沖channel輸出結果如下:
Send integer 1…
Sleep a second…
Received a integer 1.
Done.
在非緩沖Channel中,從打印數據可以看出主Goroutine中的發送操作在等待一個能夠與之配對的接收操作。配對成功后,元素值1才得以經由unbufChan通道被從主Goroutine傳遞至那個新的Goroutine.
# select與非緩沖通道
與操作緩沖通道的select相比,它被阻塞的概率一般會大很多。只有存在可配對的操作的時候,傳遞元素值的動作才能真正的開始。
示例:
> 發送操作間隔1s,接收操作間隔2s
分別向unbufChan通道發送小于10和大于等于10的整數,這樣更容易從打印結果分辨出配對的時候哪一個case被選中了。下列案例兩個case是被隨機選擇的。
~~~
package main
import (
"fmt"
"time"
)
func main(){
unbufChan := make(chan int)
sign := make(chan byte, 2)
go func(){
for i := 0; i < 10; i++ {
select {
case unbufChan <- i:
case unbufChan <- i + 10:
default:
fmt.Println("default!")
}
time.Sleep(time.Second)
}
close(unbufChan)
fmt.Println("The channel is closed.")
sign <- 0
}()
go func(){
loop:
for {
select {
case e, ok := <-unbufChan:
if !ok {
fmt.Println("Closed channel.")
break loop
}
fmt.Printf("e: %d\n",e)
time.Sleep(2 * time.Second)
}
}
sign <- 1
}()
<- sign
<- sign
}
~~~
default! //無法配對
e: 1
default!//無法配對
e: 3
default!//無法配對
e: 15
default!//無法配對
e: 17
default!//無法配對
e: 9
The channel is closed.
Closed channel.
default case會在收發操作無法配對的情況下被選中并執行。在這里它被選中的概率是50%。
- 上面的示例給予了我們這樣一個啟發:使用非緩沖通道能夠讓我們非常方便地在接收端對發送端的操作頻率實施控制。
- 可以嘗試去掉default case,看看打印結果,代碼稍作修改如下:
~~~
package main
import (
"fmt"
"time"
)
func main(){
unbufChan := make(chan int)
sign := make(chan byte, 2)
go func(){
for i := 0; i < 10; i++ {
select {
case unbufChan <- i:
case unbufChan <- i + 10:
}
fmt.Printf("The %d select is selected\n",i)
time.Sleep(time.Second)
}
close(unbufChan)
fmt.Println("The channel is closed.")
sign <- 0
}()
go func(){
loop:
for {
select {
case e, ok := <-unbufChan:
if !ok {
fmt.Println("Closed channel.")
break loop
}
fmt.Printf("e: %d\n",e)
time.Sleep(2 * time.Second)
}
}
sign <- 1
}()
<- sign
<- sign
}
~~~
> e: 0
The 0 select is selected
e: 11
The 1 select is selected
e: 12
The 2 select is selected
e: 3
The 3 select is selected
e: 14
The 4 select is selected
e: 5
The 5 select is selected
e: 16
The 6 select is selected
e: 17
The 7 select is selected
e: 8
The 8 select is selected
e: 19
The 9 select is selected
The channel is closed.
Closed channel.
**總結:上面兩個例子,第一個有default case 無法配對時執行該語句,而第二個沒有default case ,無法配對case時select將阻塞,直到某個case可以運行(上述示例是直到unbufChan數據被讀取操作),不會重新對channel或值進行求值。**
- 前言
- golang學習(一)之安裝
- Go語言學習二:Go基礎(變量、常量、數值類型、字符串、錯誤類型)
- Go語言學習三:Go基礎(iota,array,slice,map,make,new)
- Go語言學習四:struct類型
- Ubuntu 14.04/CentOS 6.5中安裝GO LANG(GO語言)
- Mac OS 安裝golang
- Mac install Thrift
- Thrift RPC 使用指南實戰(附golang&amp;PHP代碼)
- golang net/http包使用
- 冒泡排序Bubble sort-golang
- 快速排序Quick sort - golang
- Go語言學習:Channel是什么?
- Golang的select/非緩沖的Channel實例詳解
- Golang time包的定時器/斷續器
- Golang同步:鎖的使用案例詳解
- Golang同步:條件變量和鎖組合使用
- Golang同步:原子操作使用
- Golang之bytes.buffer
- Golang之字符串格式化
- Golang之反射reflect包
- Go語言配置文件解析器,類似于Windows下的INI文件.