[toc]
Channel 是我认为 Go 最灵活的部分,而我应用的方法不多,此文是我阅读《Go并发编程实战》总结下来,当作备忘。
Channel 是什么
可在多个 goroutine 从/往 一个Channel 中 receive/send 数据, 不必考虑额外的同步措施。Channel可以作为一个先入先出的队列,接收的数据和发送的数据的顺序是一致的。
buffered chann 满了,就会阻塞, 使用 make 分配结构空间及其附属空间,并完成其间的指针初始化, make 返回这个结构空间,不另外分配一个指针
1 2 3 4 5
   |  ch := make(chan Task, 3) chan T           chan<- float64   <-chan int      
 
  | 
 
以下代码检查是否关闭, 它可以用来检查Channel是否已经被关闭了。从Channel接收一个值,如果Channel关闭了或没有数据,那么ok将被置为false
1 2
   | close(chan) x, ok = <- c 
   | 
 
在一个已经 close 的 unbuffered Channel上执行读操作,会返回Channel对应类型的零值,比如 bool 型 Channel 返回 false,int 型 Channel 返回0。
- 向 close的Channel写则会触发panic。读不会导致阻塞。
 
- 往 nil Channel 中发送数据会一直被阻塞着。
 
- 对一个没有初始化的Channel进行读写操作都将发生阻塞,例子如下:
 
| 操作 | 
空值(nil) | 
已关闭 | 
| 关闭 | 
panic | 
panic | 
| 写 | 
阻塞 | 
panic | 
| 读 | 
阻塞 | 
不阻塞 | 
Example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
   | package main
  func main() { 	var c chan int 	<-c }
  $ go run testnilChannel.go fatal error: all goroutines are asleep – deadlock!
  func main() { 	var c chan int 	c <- 1 }
  $ go run testnilChannel.go fatal error: all goroutines are asleep – deadlock!
   | 
 
代码技巧
select 语句和 switch 语句一样,它不是循环,它只会选择一个 case 来处理,如果想一直处理 Channel,你可以在外面加一个无限的for循环
range c 产生的迭代值为Channel中发送的值,它会一直迭代直到 Channel 被关闭。上面的例子中如果把close(c)注释掉,程序会一直阻塞在 for 那一行。
1 2 3
   | for i := range c { 	fmt.Println(i) }
  | 
 
业务使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | // 利用 time.After 实现 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         }     } }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | main() {     ret := make(chan string, 3)     for i := 0; i < cap(ret); i++ {         go call(ret)     }     fmt.Println(<-ret) }
  func call(ret chan<- string) {     // do something     // ...     ret <- "result" }
  | 
 
1 2 3 4 5 6 7 8 9 10
   | // 最大并发数为 2 limits := make(chan struct{}, 2) for i := 0; i < 10; i++ {     go func() {         // 缓冲区满了就会阻塞在这         limits <- struct{}{}         do()         <-limits     }() }
   | 
 
1 2 3 4 5 6 7 8 9 10 11 12 13
   | func main() {     c := make(chan struct{})     for i := 0; i < 5; i++ {         go do(c)     }     close(c) }
  func do(c <-chan struct{}) {     // 会阻塞直到收到 close     <-c     fmt.Println("hello") }
  | 
 
main goroutine 通过”<-c”来等待 sub goroutine中的完成事件,sub goroutine 通过close Channel触发这一事件。当然也可以通过向 Channel 写入一个 bool 值的方式来作为事件通知。main goroutine 在 Channel c上没有任何数据可读的情况下会阻塞等待。
1 2 3 4 5 6 7 8 9 10 11 12
   | import "fmt"
  func main() {     fmt.Println("Begin doing something!")     c := make(chan bool)     go func() {         fmt.Println("Doing something…")             close(c)     }()     <-c     fmt.Println("Done!") }
   | 
 
忘记关闭的陷阱
事实上除了超时场景,其他使用协程(goroutine)的场景,也很容易因为实现不当,导致协程无法退出,随着时间的积累,造成内存耗尽,程序崩溃。
造成泄露的例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
   | func do(taskCh chan int) { 	for { 		select { 		case t := <-taskCh: 			time.Sleep(time.Millisecond) 			fmt.Printf("task %d is done\n", t) 		} 	} }
  func sendTasks() { 	taskCh := make(chan int, 10) 	go do(taskCh) 	for i := 0; i < 1000; i++ { 		taskCh <- i 	} }
  func TestDo(t *testing.T) {     t.Log(runtime.NumGoroutine())     sendTasks() 	time.Sleep(time.Second) 	t.Log(runtime.NumGoroutine()) }
  | 
 
正确的样子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | func doCheckClose(taskCh chan int) { 	for { 		select { 		case t, beforeClosed := <-taskCh: 			if !beforeClosed { 				fmt.Println("taskCh has been closed") 				return 			} 			time.Sleep(time.Millisecond) 			fmt.Printf("task %d is done\n", t) 		} 	} }
  func sendTasksCheckClose() { 	taskCh := make(chan int, 10) 	go doCheckClose(taskCh) 	for i := 0; i < 1000; i++ { 		taskCh <- i 	} 	close(taskCh) }
  func TestDoCheckClose(t *testing.T) { 	t.Log(runtime.NumGoroutine()) 	sendTasksCheckClose() 	time.Sleep(time.Second) 	runtime.GC() 	t.Log(runtime.NumGoroutine()) }
  | 
 
link: http://colobu.com/2016/04/14/Golang-Channels/
link:https://studygolang.com/articles/11320