根本介绍
通道 (chan) 是 goroutine 之间传递特定值的通信机制。它属于 通信顺序进程
并发模式(Communicating sequential processes,CSP)。go 语言中还有另一种并发模式,即共享内存多线程的传统模型。
三种类型划分
接下来探讨 chan 的不同类型,从三个角度去剖析:
缓冲 / 非缓冲(buffered/unbuffered)
chan 是一个有长度的容器,通过对长度的限定,能够定义不同类型的通道。
对于申明长度的 chan,称之为缓冲通道
ch := make(chan int, 1024) // buffered chan
对于没有申明长度的 chan,称之为非缓冲通道
ch := make(chan int) // unbuffered chan
两种类型的 chan 会有不同的个性
- 非缓冲 chan 对于读写两端的 goroutine 都会阻塞。
- 缓冲 chan 在容量为空时,读端 goroutine 会阻塞;在容量未满时,读写两端都不会阻塞;然而容量满了之后,写端会阻塞
获取 / 发送(send/receive)
对于 chan 有获取 / 发送两种操作,在申明的时候能够限定应用的形式
- 获取 / 发送双向 chan:
// chan T
type goWorker struct {
pool *Pool
task chan func()
recycleTime time.Time
}
- 只获取 chan:
// <-chan T 常见作为参数传递
func After(d Duration) <-chan Time {return NewTimer(d).C
}
- 只发送 chan:
// chan<-T 应用较少
func signalError(c chan<- error, err error) {
select {
case c <- err:
default:
}
}
敞开(close)
chan 不仅有长度,还能够被敞开,在被敞开后进行传递特定值。由此引出 chan 不同的状态:空 /empty
,满 /full
,敞开 /closed
chan 在敞开状态下,读取操作失去通道对应类型的空值,发送操作则会触发panic
done := make(chan struct{}, 1)
close(done)
d := <-done
// {}
done := make(chan struct{}, 1)
close(done)
done <- struct{}{}
//panic: send on closed channel
原理
chan 在运行时外部由构造体 runtime.hchan
实现,想要深刻理解能够浏览 源码,还能够参考浏览 draveness 的 Go 语言设计与实现。在此不做赘述。
使用指南
这是本文的外围章节,通过举例来展现 chan 的应用场景。
应用办法
chan 在理论应用时,通常会联合 for
和select
- for/range – 继续循环
例子:godoc/context
for {
select {case <-ctx.Done():
return // returning not to leak the goroutine
case dst <- n:
n++
}
}
- select – 随机抉择
例子:apache/rocketmq-client-go
select {case <-time.Tick(time.Minute * time.Duration(bp.testMinutes)):
case <-signalChan:
}
应用案例
- chan 常见用于传递根本类型和构造体。
自带库:time/After,context/WithCancel
type Timer struct {
C <-chan Time
r runtimeTimer
}
type cancelCtx struct {
Context
mu sync.Mutex // protects following fields
done chan struct{} // created lazily, closed by first cancel call
children map[canceler]struct{} // set to nil by the first cancel call
err error // set to non-nil by the first cancel call
}
- chan 不仅能够传递根本类型与构造体,还能够传递函数。
开源库:ants/worker
type goWorker struct {
// pool who owns this worker.
pool *Pool
// task is a job should be done.
task chan func()
// recycleTime will be update when putting a worker back into queue.
recycleTime time.Time
}
- 在理论工程中,通常会在主程序入口进行 signal 监听,据此实现平安退出机制。
零碎信号:os/signal
c := make(chan os.Signal, 1)
signal.Notify(c)
s := <-c
fmt.Println("Got signal:", s)
总结:在理论工程代码中,常见三种值类型
- 根本类型 & 构造体
- os.Signal
- func()
工程问题
工程开发中会遇到相似这样的问题:何时应用 chan?比照 mq 如何取舍?
整顿如下几点应用 chan 思考的因素:
- 所在 goroutine 继续运行:chan 在常驻程序的不同 goroutine 之间传递信息
- 读写端,在同一程序运行:读写两端解耦但有关联,作为整体对外提供性能
- 适度缓存,做好失落解决:作为外部信息传递通道,要思考信息失落的状况
引申探讨
无论是工程实际还是面试考查,以下引申话题都值得大家去探索。
- goroutine:要理解 chan 的原理以及灵便应用 chan,就不可绕过对 goroutine 的深刻了解
- select: 是 chan 常见的应用搭配,背地的机制值得剖析
- context:context 是工程中肯定会用到的语法实现,它的背地有 chan 的撑持
mjlzz 备注:文章供大家交换探讨,欢送斧正
相干链接
- Golang Channel 最佳实际之根本规定
- go101 Channels in Go
- golang chan 源码
- draveness 的 Go 语言设计与实现
- Golang 非阻塞 channel