关闭channel的原则
不要在接收端直接关闭channel,也不要在有多个发送端的时候主动关闭channel;
原因:1. 不能向已关闭的channel发送数据;2. 不能重复关闭已经关闭的channel;
正确关闭channel的方式:
1. 单sender,单receiver
在发送端直接关闭即可
2. 单sender,多receiver
在发送端直接关闭即可
3. 多sender,单receiver
增加一个传递关闭信号的 stopCh
,在 receiver
端通过 stopCh
下达关闭数据 dataCh
的指令。sender
监听到关闭信号后,不再向数据 dataCh
发送数据。
package main
import (
"time"
"math/rand"
"sync"
"log"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
const Max = 100000
const NumSenders = 1000
wgReceivers := sync.WaitGroup{}
wgReceivers.Add(1)
dataCh := make(chan int)
stopCh := make(chan struct{})
// senders
for i := 0; i < NumSenders; i++ {
go func() {
for {
select {
case <- stopCh:
return
default:
}
select {
case <- stopCh:
return
case dataCh <- rand.Intn(Max):
}
}
}()
}
// receiver
go func() {
defer wgReceivers.Done()
for value := range dataCh {
if value == Max-1 {
close(stopCh)
return
}
log.Println(value)
}
}()
wgReceivers.Wait()
}
4. 多sender,多receiver
第 4 种情更为复杂一点,不能够像第 3 种情况那样直接在 receiver
端关闭 stopCh
,这样会导致重复关闭已关闭的 channel
而 panic
。因此需要再加个中间人 toStop
来接收关闭 stopCh
的请求。
package main
import (
"time"
"math/rand"
"sync"
"log"
"strconv"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
const Max = 100000
const NumReceivers = 10
const NumSenders = 1000
wgReceivers := sync.WaitGroup{}
wgReceivers.Add(NumReceivers)
dataCh := make(chan int)
stopCh := make(chan struct{})
// 这个是添加的中间人,通过它来接收关闭 stopCh 的请求,做一次关闭
// 这里给缓存是 goroutine 启动时机,可能导致 select 选择,导致逻辑问题
toStop := make(chan string, 1)
var stoppedBy string
go func() {
stoppedBy = <-toStop
close(stopCh)
}()
// senders
for i := 0; i < NumSenders; i++ {
go func(id string) {
for {
value := rand.Intn(Max)
if value == 0 {
select {
case toStop <- "sender#" + id:
default:
}
return
}
// 由于 select 是随机选择的,所以先在这里尝试得知是否关闭
select {
case <- stopCh:
return
default:
}
select {
case <- stopCh:
return
case dataCh <- value:
}
}
}(strconv.Itoa(i))
}
// receivers
for i := 0; i < NumReceivers; i++ {
go func(id string) {
defer wgReceivers.Done()
for {
select {
case <- stopCh:
return
default:
}
select {
case <- stopCh:
return
case value := <-dataCh:
if value == Max-1 {
select {
case toStop <- "receiver#" + id:
default:
}
return
}
log.Println(value)
}
}
}(strconv.Itoa(i))
}
wgReceivers.Wait()
log.Println("stopped by", stoppedBy)
}