在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
https://studygolang.com/articles/819 原文链接 Introduction Go内存模型限定了一些条件 满足这些条件 才能让变量 安全地在不同的goroutine之间读写 Happens Before a = 1; To specify the requirements of reads and writes, we define happens before, a partial order on the execution of memory operations in a Go program. If event e1happens before event e2, then we say that e2 happens after e1. Also, if e1 does not happen before e2 and does not happen after e2, then we say that e1 and e2happen concurrently. 为了定义读写的需求 我们给Go程序的内存操作 定义了一个偏序“发生在X之前” 如果时间e1发生在e2之前 那么我们认为e2事件出现在e1后 同样的 如果e1不出现在e2之前 并且又不出现在e2之后 那么我们认为e1和e2是并行的 Within a single goroutine, the happens-before order is the order expressed by the program. 在同一个goroutine中 “发生在X之前”这个次序和程序中表达出来的次序是一致的 A read r of a variable v is allowed to observe a write w to v if both of the following hold: 对v的写操作w 如果想被v的读操作r观察到 需要满足下面两个条件: r does not happen before w. r不发生在w之前 想要保证读操作r 可以观察到特定的w写操作 需要确保w是r唯一可以观察到的写操作 也就是说 只有满足下面条件后 才得以保证r观察到w w happens before r. w发生在r之前 这两个条件要比之前的两个条件更加严苛 它需要没有其它的写操作和w或者r是并行发生的 Within a single goroutine, there is no concurrency, so the two definitions are equivalent: a read r observes the value written by the most recent write w to v. When multiple goroutines access a shared variable v, they must use synchronization events to establish happens-before conditions that ensure reads observe the desired writes. 在单一goroutine环境下 没有并行的说法 所以上面两对条件是相同的 读操作观察到对变量v的最近一次写 当多个goroutine同时访问共享变量v时 它们需要利用同步事件来建立“发生在X之前”这个条件 来确保读可以观察到写 The initialization of variable v with the zero value for v's type behaves as a write in the memory model. v变量用对应类型的零值初始化行为 在内存模型中 和写变量v是类似的 Reads and writes of values larger than a single machine word behave as multiple machine-word-sized operations in an unspecified order. 如果读写的值大于一个机器字 它们的行为和 多机器字操作类似 次序不确定 Synchronization 程序初始化 是在单一的goroutine环境进行的 但是 这个goroutine可以再创建其它的goroutine 它们之间并行执行 If a package p imports package q, the completion of q's init functions happens before the start of any of p's. 如果包p导入了包q q的init函数在q的初始化之前执行 The start of the function main.main happens after all init functions have finished. main.main函数在所有的初始化函数init结束后才会执行 Goroutine creation go语句会开启一个新的goroutine 并且在这个goroutine执行前就创建好了 For example, in this program: 举例来说: var a string func f() { func hello() { 在某个场合下调用hello函数 会打印“hello,world” (可能是在hello函数已经返回的情况下) Goroutine destruction 我们确定不了goroutine在退出的次序 比如在事件e之前它必须退出 举例来说: var a string func hello() { 上面这段代码 a的赋值操作并没有跟在任何的同步事件之后 所以并不能保证它会被其它的goroutine观察到 事实上 激进一点的编译器会把这段代码(go func(){a=fasd})删掉 If the effects of a goroutine must be observed by another goroutine, use a synchronization mechanism such as a lock or channel communication to establish a relative ordering. 如果想在不同的goroutine之间观察到变量的更新操作 那么需要使用一些同步的机制 比如使用锁 或者通过channel来通信 从而建立起相对的读写次序 Channel communication 使用channel进行通信 是在不同的goroutine间同步的主要方法 每个对特定channel的send操作 通常都会对应于其它goroutine中的receive操作 A send on a channel happens before the corresponding receive from that channel completes. channel的send操作 一定发生在对应的receive操作完成之前 This program: 下面这段代码: var c = make(chan int, 10) func f() { func main() { 可以确保 打印“hello, world” 写操作发生在channel c的send操作之前 send又发生在c的receive操作之前 receive发生在print之前 The closing of a channel happens before a receive that returns a zero value because the channel is closed. 关闭channel发生在接受之前 因为channel已经关闭了 mai中go f() 开启了另一个goroutine 在main中继续执行的话 会执行到 <-c 然后在channel接收这阻塞 goroutine f() 开始执行 写变量a 之后关闭channel 回到main 打印a channel在这里纯属是用来同步的 In the previous example, replacing c <- 0 with close(c) yields a program with the same guaranteed behavior. 上面那段代码中 如果用close(c)来替换c<-0 它的行为是一样的 A receive from an unbuffered channel happens before the send on that channel completes. 从无缓冲channel中接收操作 发生在该channel的发送操作结束前 This program (as above, but with the send and receive statements swapped and using an unbuffered channel): 下面这段代码 和上面代码一样 但是接收和发送操作掉了个 并且使用无缓冲channel: var c = make(chan int) func f() { 这段代码同样可以保证打印出“hello world” 对a的写操作 发生在channel c的接收操作之前 接收操作发生在相应的发送操作完成前 而发送操作则发送在打印之前 If the channel were buffered (e.g., c = make(chan int, 1)) then the program would not be guaranteed to print "hello, world". (It might print the empty string, crash, or do something else.) 如果是有缓冲的channel 那么上面那段代码就无法保证会打印“hello world”了 Locks sync包实现了两种类型的锁 sync.Mutex和sync.RWMutex For any sync.Mutex or sync.RWMutex variable l and n < m, call n of l.Unlock() happens before call m of l.Lock() returns. 对任意的sync.Mutex或者sync.RWMutex变量l n <m, 第N次调用l.Unlock 发生在 m次调用l.Lock返回前 This program: var l sync.Mutex func f() { func main() { 上述代码可以保证输出“hello,world” 第一次调用l.Unlock 发生在第二次调用l.Lock之前 而其二次调用I.Lock发生在print操作之前 For any call to l.RLock on a sync.RWMutex variable l, there is an n such that the l.RLock happens (returns) after call n to l.Unlock and the matching l.RUnlock happens before call n+1 to l.Lock. 在sync.RWMutex变量l上的任何一次l.RLock操作 会存在一个n l.RLcok发生在调用第n次l.Unlock之后 对于的l.RUnlock发生在第n+1次l.Lock调用之前 Once sync包为在多goroutine环境下初始化提供了一种安全的机制 需要使用sync包的Once类型 多个线程可以同时执行once.Do(f) 但是只有一个线程会调用f() 其它的线程会阻塞 直到f()返回 A single call of f() from once.Do(f) happens (returns) before any call of once.Do(f) returns. 通过once.Do(f)调用f发生在任何其它的once.Do(f)返回之前 In this program: var a string func setup() { func doprint() { func twoprint() { 调用两次twoprint会输出两次“hello,world” Incorrect synchronization 注意 读操作r可能会观察到和r并行的写操作w 即使出现这样的情况 也不意味着r之后的读会观察到w之前的写操作 In this program: var a, b int func f() { func g() { func main() { 上面这段代码可能会输出2 然后 输出0 This fact invalidates a few common idioms. 这个事实让很多习以为常的事情作废了 Double-checked locking is an attempt to avoid the overhead of synchronization. For example, the twoprint program might be incorrectly written as: 多检查一次锁 是避免同步问题的一种途径 例如: twoprint程序可能被写成下面这样: var a string func setup() { func doprint() { func twoprint() { 但是 这样写并不能保证 doprint中观察到对done的写 就能观察到对a的写操作 Another incorrect idiom is busy waiting for a value, as in: 另一种错误的用法是 忙等某一个变量值: var a string func setup() { func main() { 这样写的结果和上面的情况一样 这段代码可能只会打印空字符串 更惨的是 并不能保证对done的写 会在main中被观察到 因为它们之间并没有同步的事情 main中的loop并不能保证 能等到那个想要的值 There are subtler variants on this theme, such as this program. 也有一些上面代码的变种: type T struct { var g *T func setup() { func main() { 效果一样 In all these examples, the solution is the same: use explicit synchronization. |
请发表评论