go channel 使用及机制流程汇总
2012 年 3 月 24 日
协程间通讯的普通使用, 发送值给 channel
, 外部 channel
接收.
func t1() { ch := make(chan int) go func() { ch <- 1 }() // <-ch // 导致下面语句阻塞 fmt.Println("channel int", <-ch) } 复制代码
channel int 1 复制代码
channel
支持缓冲区
func t2() { ch := make(chan int, 3) go func() { ch <- 1 ch <- 2 ch <- 3 ch <- 4 // 会阻塞写不入, 除非ch已接收 close(ch) // 关闭后不能再写入, 并且缓冲区内为空时则返回零值 }() fmt.Println("buffer channel int", <-ch, <-ch, <-ch, <-ch, <-ch) } 复制代码
buffer channel int 1 2 3 4 0 复制代码
select
是专门给管道定制
func t3() { ch1 := make(chan int) ch2 := make(chan struct{}) go func() { ch1 <- 2 }() select { case <-ch1: fmt.Println("select here is ch1") case <-ch2: fmt.Println("select here is ch2") } } 复制代码
select here is ch1 复制代码
使用 for
func t4() { ch := make(chan int, 5) go func() { for i:=1; i<=5; i++ { time.Sleep(time.Millisecond * 10) ch <- i } }() for v := range ch { // 会阻塞 fmt.Println("for channel ", v) if v == 5 { break } } } 复制代码
for channel 1 for channel 2 for channel 3 for channel 4 for channel 5 复制代码
同时使用 select
和 for
func t5() { chPrint := make(chan struct{}) chStop := make(chan struct{}) go func(){ time.Sleep(time.Second * 1) chPrint <- struct{}{} time.Sleep(time.Second * 1) chPrint <- struct{}{} time.Sleep(time.Second * 1) chStop <- struct{}{} }() var sum int for { time.Sleep(time.Millisecond) select { case <-chPrint: fmt.Println("for+select now is", sum) case <-chStop: fmt.Println("for+select stop, result is", sum) return default: if sum == 10000 { fmt.Println("for+select end, result is", sum) return } sum += 1 } } } 复制代码
for+select now is 766 for+select now is 1540 for+select stop, result is 2309 复制代码
判断管道是否关闭
func t6() { ch := make(chan struct{}) go func() { close(ch) //ch <- struct{}{} // 只运行这句, 输出OK }() if _, ok := <-ch; ok { fmt.Println("if channel is ok") return } fmt.Println("if channel is bad") } 复制代码
if channel is bad 复制代码
以只发送或只接收为传递参数, 同理也可以为返回值
func t7() { ch := make(chan struct{}) chExit := make(chan struct{}) go func(chRecv <-chan struct{}) { fmt.Println("recv channel") <-chRecv chExit<- struct{}{} }(ch) go func(chSend chan<- struct{}) { fmt.Println("send channel") chSend <- struct{}{} }(ch) <-chExit } 复制代码
send channel recv channel 复制代码
channel机制流程汇总
makechan()
初始化 hchan
结构体, 如果没有缓冲区即分配 hchanSize
大小的内存并返回;而有缓冲区的情况下, 则计算管道元素类型大小并分配 hchanSize
+( elem.size
* size
)大小的内存(缓冲区是一个环形的结构设计), 最后返回 hchan
.

chansend()
向 channel
发送数据, 首先锁住当前协程, 有如下几种情况, 按顺序判断:
- 有正在等待的接收者, 就立即转发给该接收者, 释放锁并退出.
- 有可用的缓冲区就将该值移到目标缓冲区等待被接收, 释放锁并退出.
- 是非阻塞(用于select)就退出, 释放锁并退出.
- 阻塞当前协程, 并将当前协程放到发送等待队列中并释放锁, 唤醒后清理sudog.
chanrecv()
接收 channel
的数据, 首先锁住当前协程, 有如下几种情况, 按顺序判断:
-
channel
关闭并没有缓冲数据, 接收者接收的值将会是零值, 释放锁并退出. - 发送队列有发送者, 就立即接收该数据, 释放锁并退出.
- 有缓冲数据就将该数据复制给接收者, 释放锁并退出.
- 是非阻塞(用于select)就退出, 释放锁并退出.
- 阻塞当前协程, 并将当前协程放到发送等待队列中并释放锁, 唤醒后清理sudog. 跟
chansend
差不多, 多了个已关闭并没有缓冲数据的判断.
closechan
关闭 channel
, 首先也要获取锁, 关闭管道并释放所有接收和发送的队列并清醒所有sudog. 但缓冲区的数据不会清理, 随时等待被接收.
