假设 Go 程序启动了两个 goroutine:
package main
import (
"fmt"
"sync"
)
func main() {
var v int
var wg sync.WaitGroup
wg.Add(2)
go func() {
v = 1
wg.Done()
}()
go func() {
fmt.Println(v)
wg.Done()
}()
wg.Wait()
}
两个 goroutine 都对共享变量 v 进行操作。其中一个赋新值(写操作)而另一个打印变量的值(读操作)。
sync 包中的 WaitGroup 被用来等待两个非 main 的 goroutine 结束。否则,我们甚至都不能确保其中任意一个 goroutine 有被启动。
由于不同的 goroutine 是相互独立的任务,它们进行的操作之间没有任何隐含的顺序。在上面的例子中,我们不清楚会打印出 0
还是 1
。如果在 fmt.Println
被触发时,另一个 goroutine 已经执行了赋值语句 v = 1
,那么输出会是 1
。然而,在程序真正被执行之前一切都是未知的。换句话说,赋值语句和调用 fmt.Println
是无序的 —— 它们是并发的。
如果我们无法通过查看源码断定程序的行为,这是不好的。Go 的规范引入了内存操作(读和写)的偏序(partial order)关系(先行发生原则 happen before)。这个顺序使我们能够推断程序的行为。另外,这门语言中的一些机制允许程序员强制实行操作的顺序。
在单个 goroutine 中,所有操作的顺序都与它们在源码中的位置一致。
wg.Add(2)
wg.Wait()
上面例子中的函数调用是有序的,因为它们在同一个 goroutine 中 —— wg.Add(2)
先于 wg.Wait()
被执行。
1. 信道(Channel)
使用 channel 进行通信是最重要的同步方法。往 channel 中发送数据发生在接收数据之前:
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()
新的东西是 ch 这个 channel。由于接收数据发生在往 channel 中发送数据之后,而发送数据发生在给 v 赋值之后,所以上述程序的输出永远是 1
。
给 v 赋值 → 发送到 ch → 从 ch 接收 → 打印 v
第一个箭头和第三个箭头都是由同一个 goroutine 中的顺序确定的。使用 channel 进行通信带来了第二个箭头。最终,分散在两个 goroutine 中的操作是有序的。
2. sync 包
sync 包提供了同步的原语。其中能解决我们的问题的是 Mutex。sync.Mutex 类型的变量 lock 保证第二次调用 lock.Lock()
发生在第一次调用 lock.Unlock()
之后。第三次调用 lock.Lock()
发生在第二次调用 lock.Unlock()
之后。一般来说,如果 m < n,那么第 n 次调用 lock.Lock()
发生在第 m 次调用 lock.Unlock()
之后。让我们来看看在我们的同步问题中如何利用这个知识:
var v int
var wg sync.WaitGroup
wg.Add(2)
var m sync.Mutex
m.Lock()
go func() {
v = 1
m.Unlock()
wg.Done()
}()
go func() {
m.Lock()
fmt.Println(v)
wg.Done()
}()
wg.Wait()
在后续的文章中将会展示更多关于使用 channel 进行通信的内容(比如怎样使用带缓存的 channel),sync 包都提供了什么内容也会详细解释。
点赞以帮助别人发现这篇文章。如果你想得到新文章的更新,请关注我。
资源
-
The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to…
golang.org -
Errors while executing program in Go (after successful compilation and when OS process has been started) take the form…
medium.com
Golang Programming Concurrency Synchronization Goroutines
喜欢读吗?给 Michał Łowicki 一些掌声吧。
简单鼓励下还是大喝采,根据你对这篇文章的喜欢程度鼓掌吧。
via: https://medium.com/golangspec/synchronized-goroutines-part-i-4fbcdd64a4ec
作者:Michał Łowicki 译者:krystollia 校对:polaris1119
本文由 GCTT 原创翻译,Go语言中文网 首发。也想加入译者行列,为开源做一些自己的贡献么?欢迎加入 GCTT!
翻译工作和译文发表仅用于学习和交流目的,翻译工作遵照 CC-BY-NC-SA 协议规定,如果我们的工作有侵犯到您的权益,请及时联系我们。
欢迎遵照 CC-BY-NC-SA 协议规定 转载,敬请在正文中标注并保留原文/译文链接和作者/译者等信息。
文章仅代表作者的知识和看法,如有不同观点,请楼下排队吐槽
有疑问加站长微信联系(非本文作者))
