也就是说应该只在[唯一的或者最后唯一剩下的]生产者协程中关闭channel,来通知消费者已经没有值可以继续读了。
如果想要在消费端关闭channel,或者在多个生产者端关闭channel,可以使用recover机制来上个保险,避免程序因为panic崩溃。
package main
import "fmt"
func main() {
//var i chan int
c :=make( chan int,5)
c <-1
SafeClose(c)
}
func SafeClose(ch chan int) (justClosed bool) {
defer func() {
if recover() != nil {
justClosed = false
}
}()
// assume ch != nil here.
ii:= <-ch
fmt.Println(ii)
close(ch)
return true
}
使用这种方法明显违背了上面的channel关闭原则,然后性能还可以,毕竟在每个协程只会调用一次SafeClose,性能损失很小。
package main
import "fmt"
func main() {
//var i chan int
c :=make( chan int,5)
safesend(c,10)
SafeClose(c)
}
func safesend(ch chan int ,v int) (closed bool) {
defer func() {
if recover() !=nil {
closed =true
fmt.Println("true")
}
}()
ch <- v
return false
}
func SafeClose(ch chan int) (justClosed bool) {
defer func() {
if recover() != nil {
justClosed = false
fmt.Println("false")
}
}()
// assume ch != nil here.
ii:= <-ch
fmt.Println(ii)
close(ch)
return true
}
使用sync.Once
来关闭channel,确保只会关闭一次
拓展:
sync.Once
是Golang package 中使方法只执行一次的对象实现,作用和init
函数相同。但也用所不同
init
函数是在文件包首次被加载的时候执行,切只执行一次sync.Once
是在代码运行中需要的时候执行,且只执行一次
sync.Once拓展连接
package main
import (
"sync"
)
func main() {
NewMyChannel()
}
type MyChannel struct {
C chan int
once sync.Once
}
func NewMyChannel() *MyChannel {
return &MyChannel{C: make(chan int)}
}
func (mc *MyChannel) SafeClose() {
mc.once.Do(func() {
close(mc.C)
})
}
使用sync.Mutex
达到同样的目的。
package main
import (
"sync"
)
func main() {
NewMyChannel()
}
type Myhannel struct {
c chan int
closed bool
mutex sync.Mutex
}
func NewMyChannel() *Myhannel {
return &Myhannel{c: make(chan int)}
}
func (mc *Myhannel) safeClose() {
mc.mutex.Lock()
if !mc.closed {
close(mc.c)
mc.closed=true
}
mc.mutex.Unlock()
}
func (mc *Myhannel) IsClosed() bool {
mc.mutex.Lock()
defer mc.mutex.Unlock()
return mc.closed
}
要知道golang的设计者不提供SafeClose或者SafeSend方法是有原因的,他们本来就不推荐在消费端或者在并发的多个生产端关闭channel,比如关闭只读channel在语法上就彻底被禁止使用了。
多个消费者,单个生产者。这种情况最简单,直接让生产者关闭channel好了。
package main
import (
"log"
"math/rand"
"sync"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
const MaxRandomNumber = 100000
const NumReceivers =100
wgReceivers :=sync.WaitGroup{}
wgReceivers.Add(NumReceivers)
dataCh :=make(chan int,100)
go func() {
for {
if value :=rand.Intn(MaxRandomNumber);value ==0 {
//只有发送方可以安全地关闭通道。
close(dataCh)
}else {
dataCh <- value
}
}
}()
for i :=0;i<NumReceivers;i++ {
go func() {
defer wgReceivers.Done()
//接收值,直到dataCh关闭
//dataCh的值缓冲区队列为空
for value :=range dataCh{
log.Println(value)
}
}()
}
wgReceivers.Wait()
}
多个生产者,单个消费者。这种情况要比上面的复杂一点。我们不能在消费端关闭channel,因为这违背了channel关闭原则。但是我们可以让消费端关闭一个附加的信号来通知发送端停止生产数据。
package main
import (
"log"
"math/rand"
"sync"
"time"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
const MaxRandomNumber = 100000
const NumReceivers =100
wgReceivers :=sync.WaitGroup{}
wgReceivers.Add(1)
dataCh :=make(chan int,100)
//stopCh是一个附加的信号通道。它的发送方是通道数据的接收方。它的接收者是信道数据的发送者。
stopCh:=make(chan struct{})
for i :=0;i<NumReceivers;i++ {
go func() {
for {
//这里的第一个选择是尝试尽早退出goroutine。事实上,对于这个例子来说,它不是必要的,所以可以省略。
select {
case <-stopCh:
return
default:
}
// Even if stopCh is closed, the first branch in the
// second select may be still not selected for some
// loops if the send to dataCh is also unblocked.
// But this is acceptable, so the first select
// can be omitted.
select {
case <-stopCh:
return
case dataCh <- rand.Intn(MaxRandomNumber):
}
}
}()
}
go func() {
defer wgReceivers.Done()
for value :=range dataCh{
if value == MaxRandomNumber -1 {
//dataCh通道的接收方也是stopCh cahnnel的发送方。在这里关闭停止通道是安全的。
close(stopCh)
return
}
log.Println("输出",value)
}
}()
wgReceivers.Wait()
}
多个生产者,多个消费者
package main
import (
"time"
"math/rand"
"sync"
"log"
"strconv"
)
func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags(0)
// ...
const MaxRandomNumber = 100000
const NumReceivers = 10
const NumSenders = 1000
wgReceivers := sync.WaitGroup{}
wgReceivers.Add(NumReceivers)
// ...
dataCh := make(chan int, 100)
stopCh := make(chan struct{})
// stopCh is an additional signal channel.
// Its sender is the moderator goroutine shown below.
// Its reveivers are all senders and receivers of dataCh.
toStop := make(chan string, 1)
// The channel toStop is used to notify the moderator
// to close the additional signal channel (stopCh).
// Its senders are any senders and receivers of dataCh.
// Its reveiver is the moderator goroutine shown below.
var stoppedBy string
// moderator
go func() {
stoppedBy = <- toStop
close(stopCh)
}()
// senders
for i := 0; i < NumSenders; i++ {
go func(id string) {
for {
value := rand.Intn(MaxRandomNumber)
if value == 0 {
// Here, a trick is used to notify the moderator
// to close the additional signal channel.
select {
case toStop <- "sender#" + id:
default:
}
return
}
// The first select here is to try to exit the goroutine
// as early as possible. This select blocks with one
// receive operation case and one default branches will
// be optimized as a try-receive operation by the
// official Go compiler.
select {
case <- stopCh:
return
default:
}
// Even if stopCh is closed, the first branch in the
// second select may be still not selected for some
// loops (and for ever in theory) if the send to
// dataCh is also unblocked.
// This is why the first select block is needed.
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 {
// Same as the sender goroutine, the first select here
// is to try to exit the goroutine as early as possible.
select {
case <- stopCh:
return
default:
}
// Even if stopCh is closed, the first branch in the
// second select may be still not selected for some
// loops (and for ever in theory) if the receive from
// dataCh is also unblocked.
// This is why the first select block is needed.
select {
case <- stopCh:
return
case value := <-dataCh:
if value == MaxRandomNumber-1 {
// The same trick is used to notify
// the moderator to close the
// additional signal channel.
select {
case toStop <- "receiver#" + id:
default:
}
return
}
log.Println(value)
}
}
}(strconv.Itoa(i))
}
// ...
wgReceivers.Wait()
log.Println("stopped by", stoppedBy)
}
不要关闭(或将值发送到)已关闭的通道