go语言学习--channel的关闭
在使用Go channel的时候,一个适用的原则是不要从接收端关闭channel,也不要在多个并发发送端中关闭channel。换句话说,如果sender(发送者)只是唯一的sender或者是channel最后一个活跃的sender,那么你应该在sender的goroutine关闭channel,从而通知receiver(s)(接收者们)已经没有值可以读了。维持这条原则将保证永远不会发生向一个已经关闭的channel发送值或者关闭一个已经关闭的channel。
(我们将会称上面的原则为channel closing principle)
保持channel closing principle的优雅方案
channel closing principle要求我们只能在发送端进行channel的关闭,对于日常遇到的可以归结为三类
1、m个receivers,一个sender.
2、一个receiver,n个sender
3、m个receivers,n个sender
1、m个receivers,一个sender
M个receivers,一个sender,sender通过关闭data channel说“不再发送”
这是最简单的场景了,就只是当sender不想再发送的时候让sender关闭data 来关闭channel:
package main import (
"time"
"math/rand"
"sync"
"log"
) func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags() // ...
const MaxRandomNumber =
const NumReceivers = wgReceivers := sync.WaitGroup{}
wgReceivers.Add(NumReceivers) // ...
dataCh := make(chan int, ) // the sender
go func() {
for {
if value := rand.Intn(MaxRandomNumber); value == {
// the only sender can close the channel safely.
close(dataCh)
return
} else {
dataCh <- value
}
}
}() // receivers
for i := ; i < NumReceivers; i++ {
go func() {
defer wgReceivers.Done() // receive values until dataCh is closed and
// the value buffer queue of dataCh is empty.
for value := range dataCh {
log.Println(value)
}
}()
} wgReceivers.Wait()
}
2、一个receiver,n个senders
一个receiver,N个sender,receiver通过关闭一个额外的signal channel说“请停止发送”
这种场景比上一个要复杂一点。我们不能让receiver关闭data channel,因为这么做将会打破channel closing principle。但是我们可以让receiver关闭一个额外的signal channel来通知sender停止发送值:
package main import (
"time"
"math/rand"
"sync"
"log"
) func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags() // ...
const MaxRandomNumber =
const NumSenders = wgReceivers := sync.WaitGroup{}
wgReceivers.Add() // ...
dataCh := make(chan int, )
stopCh := make(chan struct{})
// stopCh is an additional signal channel.
// Its sender is the receiver of channel dataCh.
// Its reveivers are the senders of channel dataCh. // senders
for i := ; i < NumSenders; i++ {
go func() {
for {
value := rand.Intn(MaxRandomNumber) select {
case <- stopCh:
return
case dataCh <- value:
}
}
}()
} // the receiver
go func() {
defer wgReceivers.Done() for value := range dataCh {
if value == MaxRandomNumber- {
// the receiver of the dataCh channel is
// also the sender of the stopCh cahnnel.
// It is safe to close the stop channel here.
close(stopCh)
return
} log.Println(value)
}
}() // ...
wgReceivers.Wait()
}
3、m个receivers,n个sender
M个receiver,N个sender,它们当中任意一个通过通知一个moderator(仲裁者)关闭额外的signal channel来说“让我们结束游戏吧”
这是最复杂的场景了。我们不能让任意的receivers和senders关闭data channel,也不能让任何一个receivers通过关闭一个额外的signal channel来通知所有的senders和receivers退出游戏。这么做的话会打破channel closing principle。但是,我们可以引入一个moderator来关闭一个额外的signal channel。这个例子的一个技巧是怎么通知moderator去关闭额外的signal channel:
package main import (
"time"
"math/rand"
"sync"
"log"
"strconv"
) func main() {
rand.Seed(time.Now().UnixNano())
log.SetFlags() // ...
const MaxRandomNumber =
const NumReceivers =
const NumSenders = wgReceivers := sync.WaitGroup{}
wgReceivers.Add(NumReceivers) // ...
dataCh := make(chan int, )
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, )
// 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 // part of the trick used to notify the moderator
// to close the additional signal channel.
close(stopCh)
}() // senders
for i := ; i < NumSenders; i++ {
go func(id string) {
for {
value := rand.Intn(MaxRandomNumber)
if value == {
// 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.
select {
case <- stopCh:
return
default:
} select {
case <- stopCh:
return
case dataCh <- value:
}
}
}(strconv.Itoa(i))
} // receivers
for i := ; i < NumReceivers; i++ {
go func(id string) {
defer wgReceivers.Done() for {
// same as senders, the first select here is to
// try to exit the goroutine as early as possible.
select {
case <- stopCh:
return
default:
} select {
case <- stopCh:
return
case value := <-dataCh:
if value == MaxRandomNumber- {
// 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)
}
打破channel closing principle
有没有一个内置函数可以检查一个channel是否已经关闭。如果你能确定不会向channel发送任何值,那么也确实需要一个简单的方法来检查channel是否已经关闭:
package main
import "fmt"
type T int
func IsClosed(ch <-chan T) bool {
select {
case <-ch:
return true
default:
}
return false
}
func main() {
c := make(chan T)
fmt.Println(IsClosed(c)) // false
close(c)
fmt.Println(IsClosed(c)) // true
}
上面已经提到了,没有一种适用的方式来检查channel是否已经关闭了。但是,就算有一个简单的 closed(chan T) bool函数来检查channel是否已经关闭,它的用处还是很有限的,就像内置的len函数用来检查缓冲channel中元素数量一样。原因就在于,已经检查过的channel的状态有可能在调用了类似的方法返回之后就修改了,因此返回来的值已经不能够反映刚才检查的channel的当前状态了。
尽管在调用closed(ch)返回true的情况下停止向channel发送值是可以的,但是如果调用closed(ch)返回false,那么关闭channel或者继续向channel发送值就不安全了(会panic)。
The Channel Closing Principle
在使用Go channel的时候,一个适用的原则是不要从接收端关闭channel,也不要在多个并发发送端中关闭channel。换句话说,如果sender(发送者)只是唯一的sender或者是channel最后一个活跃的sender,那么你应该在sender的goroutine关闭channel,从而通知receiver(s)(接收者们)已经没有值可以读了。维持这条原则将保证永远不会发生向一个已经关闭的channel发送值或者关闭一个已经关闭的channel。
(下面,我们将会称上面的原则为channel closing principle
打破channel closing principle的解决方案
如果你因为某种原因从接收端(receiver side)关闭channel或者在多个发送者中的一个关闭channel,那么你应该使用列在Golang panic/recover Use Cases的函数来安全地发送值到channel中(假设channel的元素类型是T)
func SafeSend(ch chan T, value T) (closed bool) {
defer func() {
if recover() != nil {
// the return result can be altered
// in a defer function call
closed = true
}
}()
ch <- value // panic if ch is closed
return false // <=> closed = false; return
}
如果channel ch没有被关闭的话,那么这个函数的性能将和ch <- value接近。对于channel关闭的时候,SafeSend函数只会在每个sender goroutine中调用一次,因此程序不会有太大的性能损失。
同样的想法也可以用在从多个goroutine关闭channel中:
func SafeClose(ch chan T) (justClosed bool) {
defer func() {
if recover() != nil {
justClosed = false
}
}()
// assume ch != nil here.
close(ch) // panic if ch is closed
return true
}
很多人喜欢用sync.Once来关闭channel:
type MyChannel struct {
C chan T
once sync.Once
}
func NewMyChannel() *MyChannel {
return &MyChannel{C: make(chan T)}
}
func (mc *MyChannel) SafeClose() {
mc.once.Do(func(){
close(mc.C)
})
}
当然了,我们也可以用sync.Mutex来避免多次关闭channel:
type MyChannel struct {
C chan T
closed bool
mutex sync.Mutex
}
func NewMyChannel() *MyChannel {
return &MyChannel{C: make(chan T)}
}
func (mc *MyChannel) SafeClose() {
mc.mutex.Lock()
if !mc.closed {
close(mc.C)
mc.closed = true
}
mc.mutex.Unlock()
}
func (mc *MyChannel) IsClosed() bool {
mc.mutex.Lock()
defer mc.mutex.Unlock()
return mc.closed
}
我们应该要理解为什么Go不支持内置SafeSend和SafeClose函数,原因就在于并不推荐从接收端或者多个并发发送端关闭channel。Golang甚至禁止关闭只接收(receive-only)的channel。
go语言学习--channel的关闭的更多相关文章
- Go语言学习——channel的死锁其实没那么复杂
1 为什么会有信道 协程(goroutine)算是Go的一大新特性,也正是这个大杀器让Go为很多路人驻足欣赏,让信徒们为之欢呼津津乐道. 协程的使用也很简单,在Go中使用关键字“go“后面跟上要执行的 ...
- 大神是如何学习 Go 语言之 Channel 实现原理精要
转自: https://mp.weixin.qq.com/s/ElzD2dXWeldYkJmVVY6Djw 作者Draveness Go 语言中的管道 Channel 是一个非常有趣的数据结构,作为语 ...
- GO语言之channel
前言: 初识go语言不到半年,我是一次偶然的机会认识了golang这门语言,看到他简洁的语法风格和强大的语言特性,瞬间有了学习他的兴趣.我是很看好go这样的语言的,一方面因为他有谷歌主推,另一方面他确 ...
- go语言学习-goroutine
o 语言有一个很重要的特性就是 goroutine, 我们可以使用 goroutine 结合 channel 来开发并发程序. 并发程序指的是可以同时运行多个任务的程序,这里的同时运行并不一定指的是同 ...
- 深度解密Go语言之channel
目录 并发模型 并发与并行 什么是 CSP 什么是 channel channel 实现 CSP 为什么要 channel channel 实现原理 数据结构 创建 接收 发送 关闭 channel ...
- 技能收获与C语言学习
你有什么技能比大多人(超过90%以上)更好? 我会的东西很多,喜欢的东西太多,但是很遗憾广而不专,会而不精.学了很多东西我都是为了娱乐,因为以前我们那里过于强调学习,很多爱好也都被扼杀在摇篮里.我觉得 ...
- go语言学习笔记
Go语言学习基本类型Bool 取值范围:true,false (不可以用数字代替)Int/uint 根据平台可能为32或64位int8/uint8 长度:1字节 取值范围-128~127/0~255b ...
- 足球运动训练心得及经验分析-c语言学习调查
在准备预备作业02之前,我参考娄老师的提示,阅读了<[做中学(Learning By Doing)]之乒乓球刻意训练一年总结>一文. 在文章描述的字里行间,给予我的印象是系统.负责,娄老师 ...
- 获取技能的成功经验和关于C语言学习的调查 2015528
内容提要 你有什么技能比大多人(超过90%以上)更好?针对这个技能的获取你有什么成功的经验?与老师博客中的学习经验有什么共通之处? 有关C语言学习的调查 你是怎么学习C语言的?(作业,实验,教材,其他 ...
随机推荐
- Python 3.5 in win10 pip install Orange3
http://www.lfd.uci.edu/%7Egohlke/pythonlibs/ 下载Orange3 以及 依赖包 注意网页上标出的Orange 的依赖,以及 https://github.c ...
- LeetCode - Merge Two Binary Trees
Given two binary trees and imagine that when you put one of them to cover the other, some nodes of t ...
- 直面Java 第002期
. Java和C++同为面向对象语言,Java和C++主要区别有哪些?双方个有哪些优缺点? 解: C++ 被设计成主要用在系统性应用程序设计上的语言,对C语言进行了扩展.对于C语言这个为运行效率设计的 ...
- LG2742 【模板】二维凸包 / [USACO5.1]圈奶牛Fencing the Cows
题意 题目描述 农夫约翰想要建造一个围栏用来围住他的奶牛,可是他资金匮乏.他建造的围栏必须包括他的奶牛喜欢吃草的所有地点.对于给出的这些地点的坐标,计算最短的能够围住这些点的围栏的长度. 输入输出格式 ...
- day08 MapReduce
PS: HDFS对于MapReduce来说,HDFS就是一个就是一个客户端. PS: 离线就是 写sql,sparkh还是写sql 1. MAPREDUCE原理篇(1) Mapreduce是一个分布式 ...
- WCF 学习总结5 -- 消息拦截实现用户名验证(转)
WCF建立在基于消息的通信这一概念基础上.通过方法调用(Method Call)形式体现的服务访问需要转化成具体的消息,并通过相应的编码(Encoding)才能通过传输通道发送到服务端:服务操作执行的 ...
- word怎么在方框中打对号
最快最简单的方法,是在word里输入一个大写的R,然后选中并将字体改为wingdings2,至于那个带叉号的方框图形,可以输入大写字母T并将字体设置为windings2
- C++中的ravalue学习笔记
一.学习笔记 1. A a = 42; 会先以42为参数构造一个A类对象,然后调用拷贝构造函数来构造a,目前编译器优化掉了拷贝构造函数的调用,测试拷贝构造函数是没有被调用的,但是其权限不能为priva ...
- 递归和非递归分别实现strlen
思路:strlren主要是字符串是以'\0'为结尾标识来计算字符串的长度,所以要实现自己去写strlen也要从这方面下手. 非递归思想:应用循环的思路,以'\0'为循环结束的标识,每循环一次计数加一. ...
- egg-init 知识点
Create a simple type application $ egg-init --type simple [dest]