goroutine 的同步(第二部分)

krystollia · 2018-08-28 08:54:34 · 4246 次点击 · 预计阅读时间 4 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2018-08-28 08:54:34 的文章,其中的信息可能已经有所发展或是发生改变。

Channel 通信

第一部分介绍了发送与接收操作之间最直观的顺序关系:

向一个 Channel 中发送数据先于接收数据。

于是,我们能够控制分布于两个 goroutine 中的操作的顺序。

var v int
var wg sync.WaitGroup
wg.Add(2)
ch := make(chan int)
go func() {
    v = 1
    ch <- 1
    wg.Done()
}()
go func() {
    <-ch
    fmt.Println(v)
    wg.Done()
}()
wg.Wait()

(为了清晰,main 函数的定义和 import 语句被省略)

操作的顺序如下(xy 代表 x 发生在 y 之前):

v = 1ch <- 1<-chfmt.Println(v)

除上述这条外,还有更多的顺序规则,本篇将专注于 Channel。

发送 ↔ 接收

image

除了上述规则,还有一条规则来补充它,这条规则说接收发生于发送完成之前:

发送开始 → 接收 → 发送结束

var v, w int
var wg sync.WaitGroup
wg.Add(2)
ch := make(chan int)
go func() {
    v = 1
    ch <- 1
    fmt.Println(w)
    wg.Done()
}()
go func() {
    w = 2
    <-ch
    fmt.Println(v)
    wg.Done()
}()
wg.Wait()

因为有了这条新的规则,更多的操作之间有了顺序:

w = 2<-ch → 发送操作 ch <- 1 结束 → fmt.Println(w)

通过保证赋值操作已经完成,我们就可以解决最初关于显示变量 v 的问题。

go func() {
    v = 1
    <-ch
    wg.Done()
}()
go func() {
    ch <- 1
    fmt.Println(v)
    wg.Done()
}()

现在第二个 goroutine 会往 channel 中发送数据,它需要等待第一个 goroutine 中的赋值操作 v = 1 完成。发送操作在对应的接收操作后才能完成。

v = 1<-chch <- 1 结束 → fmt.Println(v)

关闭 channel

image

当 channel 被关闭时,接收操作会立即返回 channel 中的数据类型的零值

ch := make(chan int)
close(ch)
fmt.Println(<-ch)  // prints 0

关闭 channel 发生在从已关闭的 channel 中接收零值之前

把发送操作替换成调用自带函数 close 也可以解决我们最初的问题。

go func() {
    v = 1
    close(ch)
    wg.Done()
}()
go func() {
    <-ch
    fmt.Println(v)
    wg.Done()
}()

操作的顺序是:

v = 1close(ch)<-chfmt.Println(v)

有缓存的 channel

到目前为止我们讨论了无缓存的 channel。有缓存的 channel 在缓存未满时发送操作不会阻塞,在缓存非空时接收操作也不会阻塞:

ch := make(chan int, 1)
ch <- 1
fmt.Println(<-ch)

上述程序并不会以死锁告终,尽管在发送时并没有准备好的接受者。

对于有缓存的 channel,目前为止提到的所有规则都是成立的,除了说接收发生在发送结束之前这一条。原因很简单,(在缓存未满时),无需准备好的接受者,发送操作就可以结束。

对于容量为 c 的 channel,第 k 个接收发生在第 (k+c) 个发送完成之前。

假定缓存容量被设置为 3。前 3 个向 channel 发送数据的操作即使没有相应的接收语句也可以返回。但是为了第 4 个发送操作完成,必须有至少一个接收操作完成。

var v int
var wg sync.WaitGroup
wg.Add(2)
ch := make(chan int, 3)
go func() {
    v = 1
    <-ch
    wg.Done()
}()
go func() {
    ch <- 1
    ch <- 1
    ch <- 1
    ch <- 1
    fmt.Println(v)
    wg.Done()
}()
wg.Wait()

这一小段用有缓存的 channel 解决了我们最初的问题。


点赞以帮助别人发现这篇文章。如果你想得到新文章的更新,请关注我。

资源

保留部分版权

Golang Programming Concurrency Channels Synchronization

喜欢读吗?给 Michał Łowicki 一些掌声吧。

简单鼓励下还是大喝采,根据你对这篇文章的喜欢程度鼓掌吧。


via: https://medium.com/golangspec/synchronized-goroutines-part-ii-b1130c815c9d

作者:Michał Łowicki  译者:krystollia  校对:polaris1119

本文由 GCTT 原创编译,Go语言中文网 荣誉推出


有疑问加站长微信联系(非本文作者))

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

4246 次点击  
加入收藏 微博
被以下专栏收入,发现更多相似内容
1 回复  |  直到 2019-06-15 22:14:42
ji32k8au4a83
ji32k8au4a83 · #1 · 6年之前

信道是为了一个目标,为协程服务的一个有效且协程和同步信息结构的一个小实验性质开关或叫激流罚 我管它叫C++调度器。

添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传