11 The Go Memory Model go语言内置模型
The Go Memory Model go语言内置模型
Version of May 31, 2014
|
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:
- r does not happen before w.
- 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:
- w happens before r.
- 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 limit
channel 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 twoprint
program 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语言内置模型的更多相关文章
- GLSL语言内置的变量详解
GLSL语言内置的变量,包括内置的顶点属性(attribute).一致变量(uniform).易变变量(varying)以及常量(const),一方面加深印象,另一方面今天的文章可以为以后的编程做查询 ...
- Go语言内置包之strconv
文章引用自 Go语言内置包之strconv Go语言中strconv包实现了基本数据类型和其字符串表示的相互转换. strconv包 strconv包实现了基本数据类型与其字符串表示的转换,主要有以下 ...
- go语言内置基础类型
1.数值型(Number) 三种:整数型.浮点型和虚数型(有符号整数表示整数范围 -2n-1~2n-1-1:无符号整数表示整数范围 0~2n-1) go内置整型有:uint8, uint16, uin ...
- JavaScript语言内置对象
String(字符串对象)RegExp(正则表达式对象)Number(数字对象)Math(数学对象)Function(函数对象)Error(异常对象)Date(日期/时间对象)Boolean(布尔对象 ...
- Golang(Go语言)内置函数之copy用法
该函数主要是切片(slice)的拷贝,不支持数组 将第二个slice里的元素拷贝到第一个slice里,拷贝的长度为两个slice中长度较小的长度值 示例: s := []int{1,2,3} fmt. ...
- go语言 内置的椭圆数字签名及其验证算法
package main import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" & ...
- SQL Server 2016将内置R语言?
(此文章同时发表在本人微信公众号"dotNET每日精华文章",欢迎右边二维码来关注.) 题记:随着大数据成为一个BuzzWord,和大数据相关的技术也变得越来越火热,其中就包括R语 ...
- SQL Server 2016将内置R语言
题记:随着大数据成为一个BuzzWord,和大数据相关的技术也变得越来越火热,其中就包括R语言.而据说SQL Server 2016将会内置R语言支持? R语言作为一个存在很久的语言,在大数据热炒之后 ...
- js中内置有对象
statpot:使用mongo+bootstrap+highcharts做统计报表 最近做了一个统计项目,这个统计项目大致的需求是统计接口的访问速度.客户端会调用一个接口来记录接口的访问情况,我的需求 ...
随机推荐
- 【BZOJ2957】楼房重建(线段树)
[BZOJ2957]楼房重建(线段树) 题面 BZOJ 题解 对于整个区间维护最大斜率以及只考虑这个区间的答案 考虑如何向上合并. 首先左半段的答案是一定存在的 所以,现在的问题就是右半段能够贡献的答 ...
- Java的容器类
程序总是根据运行时才知道的某些条件去创建新对象.需要在任意时刻和任意位置创建任意数量的对象. 如果你想保存一组基本数据类型数据,建议使用数组,但是数组有固定的尺寸. 一般情况下,你在写程序时并不知道将 ...
- 【线性基/神仙题】P4151 [WC2011]最大XOR和路径
Description 给定一个无向连通图,边有边权,求一个 \(1~\sim n\) 的路径,最大化边权的异或和.如果一条边经过多次则计算多次. Input 第一行是两个整数 \(n,m\) 代表点 ...
- shell 中的流程控制关键字
if...else if [ $1x == "ab"x ]; then echo "you had enter ab" elif [ $1x == " ...
- AES-NI指令集
对于intel的AES-NI新指令集,需要i5处理器及以上的相关硬件支持.在编译时,可能会出现 /usr/lib/gcc/x86_64-linux-gnu/4.8/include/wmmintrin. ...
- Service Fabric Failover Manager
作者:潘罡 (Van Pan)@ Microsoft 什么是Failover Manager 我们回到Service Fabric系统架构图. Failover Manager是Reliability ...
- Cloudstack 虚拟机实例(四)
虚拟机实例 默认的模板并没有被下载 修改全局设置 secstorage.allowed.internal.sites 设置 ,二级存储ISO镜像和模板可以下载,IP网段 重启服务/etc/init. ...
- Java基础-日期格式化DateFormat类简介
Java基础-日期格式化DateFormat类简介 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.DateFormat类概述 DateFormat 是日期/时间格式化子类的抽象 ...
- Java基础-Date类常用方法介绍
Java基础-Date类常用方法介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.毫秒值概念 我们在查阅Date类的API文档时,会发现这样的一句话:"The cl ...
- 二维数组和指针(C语言)
二维数组和指针 二维数组和指针1.二维数组和数组元素的地址若有以下定义:int *p, a[3][4]; 1)二维数组a由若干个一维数组组成在C语言中定义的二维数组实际上是一个一维数组,这个一维数组的 ...