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
-
for{
-
select{
-
case x :=<- somechan:
- // … 使用x进行一些操作
-
-
case y, ok :=<- someOtherchan:
- // … 使用y进行一些操作,
- // 检查ok值判断someOtherchan是否已经关闭
-
-
case outputChan <- z:
- // … z值被成功发送到Channel上时
-
-
default:
- // … 上面case均无法通信时,执行此分支
- }
- }
下面是一个常见的终结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)
- }
-
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。不过无论读写都不会导致阻塞。
-
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。
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
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
带超时机制的select是常规的tip,下面是示例代码,实现30s的超时select:
func worker(start chan bool){
timeout := time.After(30* time.Second)
for{
select{
// … do some stuff
case<- timeout:
return
}
}
}
与timeout实现类似,下面是一个简单的心跳select实现:
func worker(start chan bool){
heartbeat := time.Tick(30* time.Second)
for{
select{
// … do some stuff
case<- heartbeat:
//… do heartbeat stuff
}
}
}
|
请发表评论