《Go语言圣经》——Channels

Channel

如果说goroutine是Go语音程序的并发体的话,那么channels就是它们之间的通信机制。一个channels是一个通信机制,它可以让一个goroutine通过它给另一个goroutine发送值信息。每个channel都有一个特殊的类型,也就是channels可发送数据的类>型。

一个可以发送int类型数据的channel一般写为chan int。使用内置的make函数,我们可以创建一个channel.

ch := make(chan int) // ch has type 'chan int'

和map类似,channel也是一个对应make创建的底层数据结构的引用。当我们复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值也是nil

  • 两个相同类型的channel可以使用==运算符比较。
  • 如果两个channel引用的是相同的对象,那么比较的结果为真。
  • 一个channel也可以和nil进行比较。

一个channel有发送接收两个主要操作,都是通信行为。一个发送语句将一个值从一个goroutine通过channel发送到另一个执行接收操作的goroutine。发送和接收两个操作都是用 <- 运算符。

  • 在发送语句中,<-运算符分割 channel 和 要发送的值 。
  • 在接收语句中,<-运算符写在channel对象之前。
  • 一个不使用接收结果的接收操作也是合法的。
ch <- x // a send statement
x = <-ch // a receive expression in an assignment statement
<-ch // a receive statement; result is discarded 结果被丢弃

Channel还支持close操作,用于关闭channel,随后对基于该channel的任何发送操作都将导致panic异常。
对一个已经被close过的channel执行接收操作依然可以接收到之前已经成功发送的数据;
只有发送者需要关闭通道,接收者永远不会需要关闭通道。其实你并不需要关闭每一个channel.
只有当需要告诉接收者goroutine,所有的数据已经全部发送时才需要关闭channel.
不管一个channel是否被关闭,当它没有被引用时将会被Go语言的垃圾自动回收器回收.
如果channel中已经没有数据的话讲产生一个零值的数据。
使用内置的close函数就可以关闭一个channel: close(ch),通常是配合defer使用

以最简单方式调用make函数创建的是一个无缓存的channel(同步channel),但是我们也可以指定第二个整形参数,对应channel的容量。如果channel的容量大于零,那么该channel就是带缓存的channel。

ch = make(chan int) // unbuffered channel
ch = make(chan int, 0) // unbuffered channel
ch = make(chan int, 3) // buffered channel with capacity 3

无缓存Channels

基于无缓存Channels的发送和接收操作将导致两个goroutine做一次同步操作
一个基于无缓存Channels的发送操作将导致发送者goroutine阻塞,直到另一个goroutine在相同的Channels上执行接收操作.
当发送的值通过Channels成功传输之后,两个goroutine可以继续执行后面的语句.
如果接收操作先发生,那么接收者goroutine也将阻塞,直到有另一个goroutine在相同的Channels上执行发送操作.

func main() {
    out := make(chan int)
    // 这里向out发送数据,但是并没有另外一个goroutine来接收out的值
    out <- 2
    go func(in chan int) {
        fmt.Println(<-in)
    }(out)
    // fatal error: all goroutines are asleep - deadlock!
}
关于同步&并发
  • 同步

当我们说x事件在y事件之前发生(happens
before),我们并不是说x事件在时间上比y时间更早,我们要表达的意思是要保证在此之前的事件都已经完成了.例如在此之前的更新某些变量的操作已经完成,你可以放心依赖这些已完成的事件了

  • 并发

当我们说x事件既不是在y事件之前发生也不是在y事件之后发生,我们就说x事件和y事件是并发的。这并不是意味着x事件和y事件就一定是同时发生的,我们只是不能确定这两个事件发生的先后顺序.

使用一个channel来同步两个goroutine利用无缓存channel的阻塞性质,可以让主goroutine等待后台goroutine完成工作后再退出。
func main() {
conn, err := net.Dial("tcp", "localhost:8000")
if err != nil {
log.Fatal(err)
}
done := make(chan struct{})
go func() {
io.Copy(os.Stdout, conn) // NOTE: ignoring errors
log.Println("done")
done <- struct{}{} // signal the main goroutine
}()
mustCopy(conn, os.Stdin)
conn.Close()
<-done // wait for background goroutine to finish
}

channels发送消息有两个重要方面

  • 每个消息都有一个有意义的值,channel单纯用来传输数据
  • 有时候希望强调通讯发生的时刻时,我们将它称为消息事件.这些消息事件并不携带额外的信息,它仅仅是用作两个goroutine之间的同步.这时候可以传struct{}{}(一种特殊的空结构的匿名结构)/bool/1这种值.

带缓存的Channels

带缓存的Channel内部持有一个元素队列,队列的最大容量是在调用make函数创建channel时通过第二个参数指定的.
向缓存Channel的发送操作就是向内部缓存队列的尾部插入元素,接收操作则是从队列的头部删除元素.
如果内部缓存队列是满的,那么发送操作将阻塞直到因另一个goroutine执行接收操作而释放了新的队列空间.
如果channel是空的,接收操作将阻塞直到有另一个goroutine执行

检测关闭的channel

如果发送者知道,没有更多的值需要发送到channel的话,那么让接收者也能及时知道没有多余的值可接收将是有用的,因为接收者可以停止不必要的接收等待.在发送方关闭channel即可.

if v,ok:=<-ch;!ok{break}
for v:=range ch{} // 当channel被关闭并且没有值可接收时跳出循环
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

椰子zii

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值