The Go Memory Model go语言内置模型

Version of May 31, 2014

Introduction 介绍
Advice 建议
Happens Before 在发生之前
Synchronization 同步
Initialization 初始 化
Goroutine creation goroutine创建
Goroutine destruction goroutine销毁
Channel communication channel通信
Locks 锁
Once 
Incorrect synchronization 同步的不确定性
 

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.

Advice

Programs that modify data being simultaneously accessed by multiple goroutines must serialize such access.

To serialize access, protect the data with channel operations or other synchronization primitives such as those in the sync and sync/atomic packages.

If you must read the rest of this document to understand the behavior of your program, you are being too clever.

Don't be clever.

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.

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 e1 happens 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 e2 happen concurrently.

Within a single goroutine, the happens-before order is the order expressed by the program.

A read r of a variable v is allowed to observe a write w to v if both of the following hold:

  1. r does not happen before w.
  2. There is no other write w' to v that happens after w but before 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:

  1. w happens before r.
  2. Any other write to the shared variable v either happens before w or after 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.

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.

The initialization of variable v with the zero value for v's type behaves as a write in the memory model.

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.

If a package p imports package q, the completion of q's init functions happens before the start of any of p's.

The start of the function main.main happens after all init functions have finished.

Goroutine creation

The go statement that starts a new goroutine happens before the goroutine's execution begins.

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).

Goroutine destruction

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

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.

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.

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.

A send on a channel happens before the corresponding receive from that channel completes.

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.

The closing of a channel happens before a receive that returns a zero value because the channel is closed.

In the previous example, replacing c <- 0 with close(c) yields a program with the same guaranteed behavior.

A receive from an unbuffered channel happens before the send on that channel completes.

This program (as above, but with the send and receive statements swapped and using an unbuffered 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.

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.)

The kth receive on a channel with capacity C happens before the k+Cth send from that channel completes.

This rule generalizes the previous rule to buffered channels. It allows a counting semaphore to be modeled by a buffered channel: the number of items in the channel corresponds to the number of active uses, the capacity of the channel corresponds to the maximum number of simultaneous uses, sending an item acquires the semaphore, and receiving an item releases the semaphore. This is a common idiom for limiting concurrency.

This program starts a goroutine for every entry in the work list, but the goroutines coordinate using the limitchannel to ensure that at most three are running work functions at a time.

var limit = make(chan int, 3)

func main() {
for _, w := range work {
go func(w func()) {
limit <- 1
w()
<-limit
}(w)
}
select{}
}

Locks

The sync package implements two lock data types, sync.Mutex and 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.

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.

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.

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.

A single call of f() from once.Do(f) happens (returns) before any call of once.Do(f) returns.

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 doprint runs setup once.

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.

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.

This fact invalidates a few common idioms.

Double-checked locking is an attempt to avoid the overhead of synchronization. For example, the twoprintprogram might be incorrectly written as:

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".

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.

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.

11 The Go Memory Model go语言内置模型的更多相关文章

  1. GLSL语言内置的变量详解

    GLSL语言内置的变量,包括内置的顶点属性(attribute).一致变量(uniform).易变变量(varying)以及常量(const),一方面加深印象,另一方面今天的文章可以为以后的编程做查询 ...

  2. Go语言内置包之strconv

    文章引用自 Go语言内置包之strconv Go语言中strconv包实现了基本数据类型和其字符串表示的相互转换. strconv包 strconv包实现了基本数据类型与其字符串表示的转换,主要有以下 ...

  3. go语言内置基础类型

    1.数值型(Number) 三种:整数型.浮点型和虚数型(有符号整数表示整数范围 -2n-1~2n-1-1:无符号整数表示整数范围 0~2n-1) go内置整型有:uint8, uint16, uin ...

  4. JavaScript语言内置对象

    String(字符串对象)RegExp(正则表达式对象)Number(数字对象)Math(数学对象)Function(函数对象)Error(异常对象)Date(日期/时间对象)Boolean(布尔对象 ...

  5. Golang(Go语言)内置函数之copy用法

    该函数主要是切片(slice)的拷贝,不支持数组 将第二个slice里的元素拷贝到第一个slice里,拷贝的长度为两个slice中长度较小的长度值 示例: s := []int{1,2,3} fmt. ...

  6. go语言 内置的椭圆数字签名及其验证算法

    package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" & ...

  7. SQL Server 2016将内置R语言?

    (此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:随着大数据成为一个BuzzWord,和大数据相关的技术也变得越来越火热,其中就包括R语 ...

  8. SQL Server 2016将内置R语言

    题记:随着大数据成为一个BuzzWord,和大数据相关的技术也变得越来越火热,其中就包括R语言.而据说SQL Server 2016将会内置R语言支持? R语言作为一个存在很久的语言,在大数据热炒之后 ...

  9. js中内置有对象

    statpot:使用mongo+bootstrap+highcharts做统计报表 最近做了一个统计项目,这个统计项目大致的需求是统计接口的访问速度.客户端会调用一个接口来记录接口的访问情况,我的需求 ...

随机推荐

  1. Alpha 冲刺 —— 十分之三

    队名 火箭少男100 组长博客 林燊大哥 作业博客 Alpha 冲鸭鸭鸭! 成员冲刺阶段情况 林燊(组长) 过去两天完成了哪些任务 协调各成员之间的工作 协助后端界面的开发 搭建项目运行的服务器环境 ...

  2. 【Learning】插头DP

    简介 插头DP(轮廓线DP)是用来解决网格图回路问题的一种算法. 插头DP解决的经典问题就是统计经过所有格子的哈密顿回路条数,某些格子有障碍. ​ 如果问题稍微进阶一点的话,不一定要求路径是回路.路径 ...

  3. 【codeforces 778C】 Peterson Polyglot

    http://codeforces.com/problemset/problem/778/C (题目链接) 题意 给出一个字典树,问删掉哪一层以后,得到的字典树最小. Solution 直接对于每一层 ...

  4. Unhandled rejection RangeError: Maximum call stack size exceededill install loadIdealTree

    npm安装时候报这个错误,解决方法如下: 删除项目中的 package-lock.json 文件 和 node_modules 文件夹,然后再尝试 npm install 详细参考

  5. GoLand语言快捷键

    快捷键 作用 备注 ctrl + n 导航到类名 ctrl + shift + n 导航到文件 ctrl + e/ctrl + shift + e 打开到最近的文件/打开最近修改的文件 ctrl + ...

  6. PHP发送HTTP请求的几种方式

    转发:https://blog.tanteng.me/2017/07/php-curl-guzzlehttp/ 1)PHP开发中我们常用CURL 方式封装 HTTP请求,什么是CURL? CURL 是 ...

  7. Java利用poi生成word(包含插入图片,动态表格,行合并)

    转(小改): Java利用poi生成word(包含插入图片,动态表格,行合并) 2018年12月20日 09:06:51 wjw_11093010 阅读数:70 Java利用poi生成word(包含插 ...

  8. Kubernetes Deloyment实现滚动更新

    目录 滚动更新简介 使用kubectl rolling-update更新RC Deployment的rolling-update 滚动更新简介 当kubernetes集群中的某个服务需要升级时,传统的 ...

  9. Mac 远程连接windows桌面工具parallels client

    https://www.cnblogs.com/cssfirefly/p/9644940.html Parallels Client 客户端是免费,App Store 直接下载 复制文件:用共享文件夹 ...

  10. java 修饰符总结

    java中的修饰符分为类修饰符,字段修饰符,方法修饰符.根据功能的不同,主要分为以下几种.    1.权限访问修饰符    public,protected,default,private,这四种级别 ...