https://studygolang.com/articles/819 原文链接

Introduction
The Go memory model specifies the conditions under which reads of a variable in one goroutine can be guaranteed to observe values produced by writes to the same variable in a different goroutine.

Go内存模型限定了一些条件 满足这些条件 才能让变量 安全地在不同的goroutine之间读写

Happens Before
Within a single goroutine, reads and writes must behave as if they executed in the order specified by the program. That is, compilers and processors may reorder the reads and writes executed within a single goroutine only when the reordering does not change the behavior within that goroutine as defined by the language specification. Because of this reordering, the execution order observed by one goroutine may differ from the order perceived by another. For example, if one goroutine executes a=1; b=2;, another might observe the updated value of b before the updated value of a.

a = 1;
b = 2;
如果只有一个goroutine 读写行为和它们在程序中的位置一致 就是说 编译器和处理器只能在保证读写在程序中的正确行为时 比如读写顺序 才有可能对读写进行优化 举例来说 在一个goroutine中执行 另一个goroutine中可能会在a之前察觉到b的更新操作

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之前
There is no other write w' to v that happens after w but before r. 没有其它的写操作w` 发生在w之后 r之前
To guarantee that a read r of a variable v observes a particular write w to v, ensure that w is the only write r is allowed to observe. That is, r is guaranteed to observe w if both of the following hold:

想要保证读操作r 可以观察到特定的w写操作 需要确保w是r唯一可以观察到的写操作 也就是说 只有满足下面条件后 才得以保证r观察到w

w happens before r. w发生在r之前
Any other write to the shared variable v either happens before w or after r. 任何其它对v的写 要么发生在w之前 要么在r之后
This pair of conditions is stronger than the first pair; it requires that there are no other writes happening concurrently with w or 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
Initialization
Program initialization runs in a single goroutine, but that goroutine may create other goroutines, which run concurrently.

程序初始化 是在单一的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
The go statement that starts a new goroutine happens before the goroutine's execution begins.

go语句会开启一个新的goroutine 并且在这个goroutine执行前就创建好了

For example, in this program:

举例来说:

var a string

func f() {
print(a)
}

func hello() {
a = "hello, world"
go f()
}
calling hello will print "hello, world" at some point in the future (perhaps after hello has returned).

在某个场合下调用hello函数 会打印“hello,world” (可能是在hello函数已经返回的情况下)

Goroutine destruction
The exit of a goroutine is not guaranteed to happen before any event in the program. For example, in this program:

我们确定不了goroutine在退出的次序 比如在事件e之前它必须退出 举例来说:

var a string

func hello() {
go func() { a = "hello" }()
print(a)
}
the assignment to a is not followed by any synchronization event, so it is not guaranteed to be observed by any other goroutine. In fact, an aggressive compiler might delete the entire go statement.

上面这段代码 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 communication is the main method of synchronization between goroutines. Each send on a particular channel is matched to a corresponding receive from that channel, usually in a different goroutine.

使用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)
var a string

func f() {
a = "hello, world"
c <- 0
}

func main() {
go f()
<-c
print(a)
}
is guaranteed to print "hello, world". The write to a happens before the send on c, which happens before the corresponding receive on c completes, which happens before the print.

可以确保 打印“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)
var a string

func f() {
a = "hello, world"
<-c
}
func main() {
go f()
c <- 0
print(a)
}
is also guaranteed to print "hello, world". The write to a happens before the receive on c, which happens before the corresponding send on c completes, which happens before the print.

这段代码同样可以保证打印出“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
The sync package implements two lock data types, sync.Mutex and sync.RWMutex.

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
var a string

func f() {
a = "hello, world"
l.Unlock()
}

func main() {
l.Lock()
go f()
l.Lock()
print(a)
}
is guaranteed to print "hello, world". The first call to l.Unlock() (in f) happens before the second call to l.Lock() (in main) returns, which happens before the print.

上述代码可以保证输出“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
The sync package provides a safe mechanism for initialization in the presence of multiple goroutines through the use of the Once type. Multiple threads can execute once.Do(f) for a particular f, but only one will run f(), and the other calls block until f() has returned.

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
var once sync.Once

func setup() {
a = "hello, world"
}

func doprint() {
once.Do(setup)
print(a)
}

func twoprint() {
go doprint()
go doprint()
}
calling twoprint causes "hello, world" to be printed twice. The first call to twoprint runs setup once.

调用两次twoprint会输出两次“hello,world”

Incorrect synchronization
Note that a read r may observe the value written by a write w that happens concurrently with r. Even if this occurs, it does not imply that reads happening after r will observe writes that happened before w.

注意 读操作r可能会观察到和r并行的写操作w 即使出现这样的情况 也不意味着r之后的读会观察到w之前的写操作

In this program:

var a, b int

func f() {
a = 1
b = 2
}

func g() {
print(b)
print(a)
}

func main() {
go f()
g()
}
it can happen that g prints 2 and then 0.

上面这段代码可能会输出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
var done bool

func setup() {
a = "hello, world"
done = true
}

func doprint() {
if !done {
once.Do(setup)
}
print(a)
}

func twoprint() {
go doprint()
go doprint()
}
but there is no guarantee that, in doprint, observing the write to done implies observing the write to a. This version can (incorrectly) print an empty string instead of "hello, world".

但是 这样写并不能保证 doprint中观察到对done的写 就能观察到对a的写操作

Another incorrect idiom is busy waiting for a value, as in:

另一种错误的用法是 忙等某一个变量值:

var a string
var done bool

func setup() {
a = "hello, world"
done = true
}

func main() {
go setup()
for !done {
}
print(a)
}
As before, there is no guarantee that, in main, observing the write to done implies observing the write to a, so this program could print an empty string too. Worse, there is no guarantee that the write to done will ever be observed by main, since there are no synchronization events between the two threads. The loop in main is not guaranteed to finish.

这样写的结果和上面的情况一样 这段代码可能只会打印空字符串 更惨的是 并不能保证对done的写 会在main中被观察到 因为它们之间并没有同步的事情 main中的loop并不能保证 能等到那个想要的值

There are subtler variants on this theme, such as this program.

也有一些上面代码的变种:

type T struct {
msg string
}

var g *T

func setup() {
t := new(T)
t.msg = "hello, world"
g = t
}

func main() {
go setup()
for g == nil {
}
print(g.msg)
}
Even if main observes g != nil and exits its loop, there is no guarantee that it will observe the initialized value for g.msg.

效果一样

In all these examples, the solution is the same: use explicit synchronization.

【翻译】go memory model的更多相关文章

  1. memory model

    最近看C++11 atomic发现对memory_order很是不理解,memory_order_relaxed/memory_order_consume/memory_order_acquire/m ...

  2. Java (JVM) Memory Model – Memory Management in Java

    原文地址:http://www.journaldev.com/2856/java-jvm-memory-model-memory-management-in-java Understanding JV ...

  3. 还是说Memory Model,gcc的__sync_synchronize真是太坑爹了

    还是说Memory Model,gcc的__sync_synchronize真是太坑爹了! 时间 2012-01-29 03:18:35  IT牛人博客聚合网站 原文  http://www.udpw ...

  4. Keil中Memory Model和Code Rom Size说明

    C51中定义变量时如果省略存储器类型,Keil C51编译系统则会按编译模式SMALL.COMPACT和LARGE所规定的默认存储器类型去指定变量的存储区域,无论什么存储模式都可以声明变量在任何的80 ...

  5. 当我们在谈论JMM(Java memory model)的时候,我们在谈论些什么

    前面几篇中,我们谈论了synchronized.final以及voilate的用法和底层实现,都绕不开一个话题-Java内存模型(java memory model,简称JMM).Java内存模型是保 ...

  6. 并发研究之Java内存模型(Java Memory Model)

    Java内存模型JMM java内存模型定义 上一遍文章我们讲到了CPU缓存一致性以及内存屏障问题.那么Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬 ...

  7. java学习:JMM(java memory model)、volatile、synchronized、AtomicXXX理解

    一.JMM(java memory model)内存模型 从网上淘来二张图: 上面这张图说的是,在多核CPU的系统中,每个核CPU自带高速缓存,然后计算机主板上也有一块内存-称为主内(即:内存条).工 ...

  8. CUDA ---- Memory Model

    Memory kernel性能高低是不能单纯的从warp的执行上来解释的.比如之前博文涉及到的,将block的维度设置为warp大小的一半会导致load efficiency降低,这个问题无法用war ...

  9. 11 The Go Memory Model go语言内置模型

    The Go Memory Model go语言内置模型 Version of May 31, 2014 Introduction 介绍 Advice 建议 Happens Before 在发生之前 ...

随机推荐

  1. 过渡与动画 - 缓动效果&基于贝塞尔曲线的调速函数

    难题 给过渡和动画加上缓动效果是一种常见的手法(比如具有回弹效果的过渡过程)是一种流行的表现手法,可以让界面显得更加生动和真实:在现实世界中,物体A点到B点往往也是不完全匀速的 以纯技术的角度来看,回 ...

  2. 时间复杂度O(n^2)和O(nlog n)差距有多大?

    0. 时间复杂度 接触到算法的小伙伴们都会知道时间复杂度(Time Complexity)的概念,这里先放出(渐进)时间复杂度的定义: 假设问题规模是\(n\),算法中基本操作重复执行的次数是\(n\ ...

  3. ExtJs 编译

    前台使用Extjs加载源码的话是非常庞大的,编译之后就只加载一个app.js文件.这种技能如果不知道的话怕别人骂我不是个女程序员.哈哈哈哈哈. 打开cmd,进入程序Extjs的文件夹,如我的程序Ext ...

  4. squid代理http和https方式上网的操作记录

    需求说明:公司IDC机房有一台服务器A,只有内网环境:192.168.1.150现在需要让这台服务器能对外访问,能正常访问http和https请求(即80端口和443端口)操作思路:在IDC机房里另找 ...

  5. SCRUM 12.22

    周一,大家现在课程也比较少,今天都在非常努力地写代码. 任务分配如往常一样,我们现在基本将工作的重心放在完善已有的组件上. 成员 任务 彭林江 落实API 牛强 落实意见反馈功能测试 高雅智 测试已完 ...

  6. 基于UML的需求分析和系统设计

    小序: 从学生时代就接触到UML,几年的工作中也没少使用,各种图形的概念.图形的元素和属性,以及图形的画法都不能说不熟悉.但是怎样在实际中有效地使用UML使之发挥应有的作用,怎样捕捉用户心中的需求并转 ...

  7. 传参在mybatis的sql映射文件中正确获取

    1.单个参数: 非自定义对象 传参:getStuById(Integer id): 取值:#{id} 单个基本类型参数,随便取值都行:#{ok} 对象: 传参:saveStudent(Student ...

  8. Android控件第7类——对话框

    1.AlertDialog AlertDialog用来生成对话框,功能十分强大. AlertDialog可以分成4个组成部分:标题栏上的图标,标题区,文本区,按钮区. 使用方法: 创建AlertDia ...

  9. WMS与MES集成

    ERP的物流管理只偏重于帐务的管理,对现场实物流的及时性和精细程度管理均不够,无法满足企业日益精细化管理的需求,WMS可以弥补这个缺陷,WMS采用条码技术及时反应物流过程中的变化,同时将存量细化到货架 ...

  10. Python模块笔记

    __name__属性 一个模块被另一个程序第一次引入时,其主程序将运行.如果我们想在模块被引入时,模块中的某一程序块不执行,我们可以用__name__属性来使该程序块仅在该模块自身运行时执行. #!/ ...