个人笔记
SongPinru 的小仓库
正确使用channel
channel的特性
- channel会阻塞输入或者输出端,直到有goroutine接收或者写入
- channel只能由produce端close(调用close函数),输出端close编译不通过(必须要有produce的权限)
- channel能且只能close一次,第二次会报panic
- channel关闭之后不能写入,写入会报panic
- channel的接受端可以有两个返回值,如果第二个返回值为false,代表channel关闭
- 已关闭的channel也可用读取,但是标志位(第二个返回值)会是false
使用场景
spsc
spsc(一个生产者一个消费者):
这是理想情况,只需要produce端close即可
func send(ch chan<- int) {
ch <- 1
close(ch)
}
func consume(ch <-chan int) {
num := <-ch
fmt.Println(num)
}
spmc
spmc(一个生产者多个消费者):
比较简单常见的情况,也是只需要produce端close,但是consume端需要判断标志位(否则使用的是零值,可能会造成错误)
func send(ch chan<- int) {
ch <- 1
close(ch)
}
func consume(ch <-chan int) {
num ,ok:= <-ch
if ok{
fmt.Println(num)
}
}
mpsc
mpsc(多个生产者一个消费者):
略微复杂的情况,这里需要由consume端负责close(意味着consume需要使用chan
,而不是<-chan
),同时produce端需要使用recover()处理panic
func send(ch chan<- int) {
defer func() {
if err := recover();err!=nil{
fmt.Println(err)
}
}()
ch <- 1
}
func consume(ch chan int) {
num ,ok:= <-ch
if ok{
fmt.Println(num)
}
close(ch)
}
这个方法还是不太优雅,最好使用两个channel(或者是sync.waitGroup,在另一个线程中关闭),使用另一个channel通知producer不要写入了,有多少个producer就发送多少条,在最后一个producer关闭channel
func send(ch chan<- int,flag <-chan bool) {
for {
select {
case f, _ := <-flag:
if f == 3 {
close(ch)
fmt.Println("successes closed")
return
}
fmt.Println("closed")
return
case ch <- 1:
fmt.Println("send")
}
}
}
func consume(ch <-chan int,flag chan<- bool) {
for message := range ch {
fmt.Println("received :",message)
for i := 0; i < 4; i++ {
flag<-i
}
close(flag)
}
}
mpmc
mpmc(多个生产者多个消费者):
最复杂的情况,这种场景下produce和consume端都可能会close,具体视业务场景而定。此时produce端需要处理panic,consume端需要处理标志位和panic
func send(ch chan<- int) {
defer func() {
if err := recover();err!=nil{
fmt.Println(err)
}
}()
ch <- 1
//close(ch)
}
func consume(ch chan int) {
defer func() {
if err := recover();err!=nil{
fmt.Println(err)
}
}()
num ,ok:= <-ch
if ok{
fmt.Println(num)
}
//close(ch)
}
总结:
* produce端只给`chan<-`权限即可
* consume端如果需要close给`chan`,确定不需要给`<-chan`,最好使用for来取数据
* produce端最好都做recover处理(具体使用时可以`fmt.Sprint(err)=="send on closed channel"`来再次上抛不属于channel的panic)
* consume端最好都处理标志位,如果有写权限(`chan`),可能也要处理panic
* 最后一条:尽量手动关闭channel
多生产者多消费者,最好使用辅助channel通知生产者停止消费数据,可以使用另一个goroutine或者是最后一个生产者关闭channel
channel的关闭原则:
- 不要在消费者端关闭channel
- 不要在有多个并行的生产者时关闭channel
- 应该只在唯一或者最后一个生产者协程中关闭channel
PS
time包下有两个特殊的channel,使用After和Tick来实现定时