1,等待一個事件
<-
ch 將一直阻塞,直到ch被關閉 或者 ch中可以取出值 為止
所以到第17行之后會去執行go后面的func()匿名函數,在里面給ch賦值后(或者close(ch))后,才能繼續往后執行
- package main
- import(
- "fmt"
- )
- func main(){
- fmt.Println("Begin doing something!")
- ch := make(chan int)
- go func(){
- fmt.Println("Doing something…")
- ch <-22
- //close(ch)
- }()
- <-ch //此處將被阻塞,直到ch被關閉 或 有值可以取出
- fmt.Println("Done!")
- }
2,協同多個Goroutines
同上,close channel還可以用於協同多個Goroutines,比如下面這個例子,我們創建了100個Worker Goroutine,這些Goroutine在被創建出來后都阻塞在"<-start"上,直到我們在main goroutine中給出開工的信號:"close(start)",這些goroutines才開始真正的並發運行起來。
- package main
- import"fmt"
- func worker(start chan bool, index int){
- <-start // 從start中取出數據后,調用20行的case語句
- fmt.Println("This is Worker:", index)
- }
- func main(){
- start := make(chan bool)
- for i :=1; i <=10; i++{
- go worker(start, i)
- }
- //給start賦值10次,讓worker方法執行10次
- for i :=1; i <=10; i++{
- start <-true//給start賦值一次,便執行worker函數一次
- }
- v :=1
- //select 被一直阻塞直到start中數據被取出
- select{ //deadlock we expected
- case<-start:
- fmt.Print(v)
- v++
- }
- }
3,Select
-
select常與for一起使用
- for{
- select{
- case x :=<- somechan:
- // … 使用x進行一些操作
- case y, ok :=<- someOtherchan:
- // … 使用y進行一些操作,
- // 檢查ok值判斷someOtherchan是否已經關閉
- case outputChan <- z:
- // … z值被成功發送到Channel上時
- default:
- // … 上面case均無法通信時,執行此分支
- }
- }
-
終結woker
下面是一個常見的終結sub worker goroutines的方法,
每個worker goroutine通過select監視一個die channel來及時獲取main goroutine的退出通知。
- package main
- import(
- "fmt"
- "time"
- )
- func worker(die chan bool, index int){
- fmt.Println("Begin: This is Worker:", index)
- for{
- select{
- //case xx:
- //做事的分支
- case<-die: //到這里就被阻塞,運行main中的close后輸出 done
- fmt.Println("Done: This is Worker:", index)
- return
- }
- }
- }
- func main(){
- die:= make(chan bool)
- for i :=1; i <=10; i++{
- go worker(die, i)
- }
- time.Sleep(time.Second*5)
- close(die)
- select{}//deadlock we expected
- }
-
終結驗證
有時候終結一個worker后,main goroutine想確認worker routine是否真正退出了,可采用下面這種方法:
- package main
- import(
- "fmt"
- //"time"
- )
- func worker(die chan bool){
- fmt.Println("Begin: This is Worker")
- for{
- select{
- //case xx:
- //做事的分支
- case<-die://這里等待27行的賦值語句,如果沒有賦值,一直阻塞
- fmt.Println("Done: This is Worker")
- die<-true
- return
- }
- }
- }
- func main(){
- die:= make(chan bool)
- go worker(die)
- die<-true
- istrue :=<-die//這里等待16行的賦值,賦值完畢后程序繼續執行
- fmt.Println("Worker goroutine has been terminated", istrue)
- }
-
關閉的Channel永遠不會阻塞
- package main
- import"fmt"
- func main(){
- cb := make(chan bool)
- close(cb)//當cb被關閉后,所有的取值操作將不會被阻塞
- x :=<-cb
- fmt.Printf("%#v\n", x)
- x, ok :=<-cb
- fmt.Printf("%#v %#v\n", x, ok)
- ci := make(chan int)
- close(ci)
- y :=<-ci //即使ci被關閉,照樣可以從ci中取數據,取得0
- fmt.Printf("%#v\n", y)
- cb <-true
- }
false
false false
0
panic: send on closed channel
19行將報異常
可以看到在一個已經close的unbuffered channel上執行讀操作,回返回channel對應類型的零值,比如bool型channel返回false,int型channel返回0。但向close的channel寫則會觸發panic。不過無論讀寫都不會導致阻塞。
-
(5)關閉帶緩存的channel
- package main
- import"fmt"
- func main(){
- c := make(chan int,3)
- c <-15
- c <-34
- c <-65
- close(c)
- fmt.Printf("%d\n",<-c)//channel被關閉后,照樣可以從channel中取出數據,只是不能向其中寫數據
- fmt.Printf("%d\n",<-c)
- fmt.Printf("%d\n",<-c)
- fmt.Printf("%d\n",<-c)//當channel中數據全部被取出時,將輸出0
- c <-1
- }
15
34
65
0
panic: runtime error: send on closed channel
16行將報異常
可以看出帶緩沖的channel略有不同。盡管已經close了,但我們依舊可以從中讀出關閉前寫入的3個值。第四次讀取時,則會返回該channel類型的零值。向這類channel寫入操作也會觸發panic。
-
range
Golang中的range常常和channel並肩作戰,它被用來從channel中讀取所有值。下面是一個簡單的實例:
- package main
- import"fmt"
- func generator(strings chan string){
- strings <-"Five hour's New York jet lag"
- strings <-"and Cayce Pollard wakes in Camden Town"
- strings <-"to the dire and ever-decreasing circles"
- strings <-"of disrupted circadian rhythm."
- close(strings)
- }
- func main(){
- strings := make(chan string)
- go generator(strings)
- for s := range strings {
- fmt.Printf("%s\n", s)//這里的s 相當於 <- strings,只有賦值之后才能讀取到,否則一直阻塞
- }
- fmt.Printf("\n")
- }
4. 隱藏狀態
沒有緩存 的chan默認為1個單位的緩存
package main
import"fmt"
func newUniqueIDService()<-chan string{
id := make(chan string)
go func(){
var counter int64 =0
for{
id <- fmt.Sprintf("%x", counter)
counter +=1
}
}()
return id
}
func main(){
id := newUniqueIDService()
for i :=0; i <10; i++{
fmt.Println(<-id) //被阻塞,直到id被賦值
}
}
newUniqueIDService通過一個channel與main goroutine關聯,main goroutine無需知道uniqueid實現的細節以及當前狀態,只需通過channel獲得最新id即可。
5,select的default分支的實踐用法
-
讀取時為空
idle := make(chan []byte,5)//用一個帶緩沖的channel構造一個簡單的隊列
select{
case<-idle:
//嘗試從idle隊列中讀取
fmt.Println("讀取")
default://隊列空,分配一個新的buffer
fmt.Println("寫入")
}
-
寫入時已滿
package main
import(
"fmt"
)
func main(){
idle := make(chan []int,10)//用一個帶緩沖的channel構造一個簡單的隊列
var b =[]int{2,1}
select{
case idle <- b://嘗試向隊列中插入一個buffer
fmt.Println(idle)
default://隊列滿?
println("隊列滿")
}
}
6,Nil Channels
-
nil channels阻塞 : 對一個沒有初始化的channel進行讀寫操作都將發生阻塞
package main
func main(){
var c chan int
c <-1
}
將發生阻塞
-
循環輸出
package main
import"fmt"
import"time"
func main(){
var c1, c2 chan int= make(chan int), make(chan int)
go func(){
time.Sleep(time.Second*5)
c1 <-5
close(c1)
}()
go func(){
time.Sleep(time.Second*7)
c2 <-7
close(c2)
}()
for{
select{
case x :=<-c1://在等待5s后,把值取出,如果close(c1)那么,將一直輸出0
fmt.Println(x)
case x :=<-c2://在等待7s后,輸出c2的值並退出
fmt.Println(x)
return
}
}
fmt.Println("over")
}
輸出的結果是
5
0
0
0
...
7
改為交替輸出
package main
import"fmt"
import"time"
func main(){
var c1, c2 chan int= make(chan int), make(chan int)
go func(){
time.Sleep(time.Second*5)
c1 <-5
close(c1)
}()
go func(){
time.Sleep(time.Second*7)
c2 <-7
close(c2)
}()
for{
select{
case x, ok :=<-c1://如果c1未被關閉,則輸出x,如果x關閉,c1=nil
if!ok {
c1 =nil
}else{
fmt.Println(x)
}
case x, ok :=<-c2://如果c2未被關閉,則輸出x,如果x關閉,c2=nil
if!ok {
c2 =nil
}else{
fmt.Println(x)
}
}
if c1 ==nil&& c2 ==nil{
break//如果=nil那么推出
}
}
fmt.Println("over")
}
5
7
over
7.Timers
-
超時機制Timeout
帶超時機制的select是常規的tip,下面是示例代碼,實現30s的超時select:
func worker(start chan bool){
timeout := time.After(30* time.Second)
for{
select{
// … do some stuff
case<- timeout:
return
}
}
}
-
心跳HeartBeart
與timeout實現類似,下面是一個簡單的心跳select實現:
func worker(start chan bool){
heartbeat := time.Tick(30* time.Second)
for{
select{
// … do some stuff
case<- heartbeat:
//… do heartbeat stuff
}
}
}