Channel关闭原则
也就是说应该只在[唯一的或者最后唯一剩下]的生产者协程中关闭channel,来通知消费者已经没有值可以继续读了。只要坚持这个原则,就可以确保向一个已经关闭的channel发送数据的情况不可能发生。
暴力关闭channel的正确方法
如果想要在消费端关闭channel,或者在多个生产者端关闭channel,可以使用recover机制来上个保险,避免程序因为panic而崩溃。
使用这种方法明显违背了上面的channel关闭原则,然后性能还可以,毕竟在每个协程只会调用一次SafeClose,性能损失很小。
同样也可以在生产消息的时候使用recover方法。
礼貌的关闭channel方法
还有不少人经常使用用sync.Once来关闭channel,这样可以确保只会关闭一次
同样我们也可以使用sync.Mutex达到同样的目的。
要知道golang的设计者不提供SafeClose或者SafeSend方法是有原因的,他们本来就不推荐在消费端或者在并发的多个生产端关闭channel,比如关闭只读channel在语法上就彻底被禁止使用了。
优雅的关闭channel的方法
上文的SafeSend方法一个很大的劣势在于它不能用在select块的case语句中。而另一个很重要的劣势在于像我这样对代码有洁癖的人来说,使用panic/recover和sync/mutex来搞定不是那么的优雅。下面我们引入在不同的场景下可以使用的纯粹的优雅的解决方法。
多个消费者,单个生产者。这种情况最简单,直接让生产者关闭channel好了。
看代码,上乎知搜【码洞】
多个生产者,单个消费者。这种情况要比上面的复杂一点。我们不能在消费端关闭channel,因为这违背了channel关闭原则。但是我们可以让消费端关闭一个附加的信号来通知发送端停止生产数据。
看代码,上乎知搜【码洞】
就上面这个例子,生产者同时也是退出信号channel的接受者,退出信号channel仍然是由它的生
多个生产者,多个消费者
这是最复杂的一种情况,我们既不能让接受端也不能让发送端关闭channel。我们甚至都不能让接受者关闭一个退出信号来通知生产者停止生产。因为我们不能违反channel关闭原则。但是我们可以引入一个额外的协调者来关闭附加的退出信号channel。
看代码,上乎知搜【码洞】
以上三种场景不能涵盖全部,但是它们是最常见最通用的三种场景,基本上所有的场景都可以划分为以上三类。
尾声
没有任何场景值得你去打破channel关闭原则,如果你遇到这样的一种特殊场景,还是建议你好好思考一下自己设计,是不是该重构一下了。