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做统计报表 最近做了一个统计项目,这个统计项目大致的需求是统计接口的访问速度.客户端会调用一个接口来记录接口的访问情况,我的需求 ...
随机推荐
- Vue设置不同的环境发布程序
原文地址: http://www.cnblogs.com/JimmyBright/p/7307486.html 通常应用程序上线都会经过开发环境.测试环境.生产环境三个阶段,三个环境通常会对应有三个不 ...
- centos_radhat升级系统
Centos/Redhat系统下,Yum升级操作系统版本方法: 很多情况下,系统安装的时候是一个版本,官方很快就会出新的版本,提高系统的安全性,以及一些漏洞补丁 通过以下步骤可以平滑将系统升级,例如C ...
- 【纪中集训2019.3.12】Mas的仙人掌
题意: 给出一棵\(n\)个点的树,需要加\(m\)条边,每条边脱落的概率为\(p_{i}\) ,求加入的边在最后形成图中仅在一个简单环上的边数的期望: \(1 \le n \ , m \le 1 ...
- 20170520 DP阶段总结
DP的力量不是无穷的. 但是,因为它叫做“动态规划”,它在OI界如鱼得水.这个“动态”不是指“离线”与“在线”,也不是什么“可持久化”.它只是把问题抽象为一个个“阶段”,在每一个“阶段”中作出或繁或简 ...
- Python之旅:并发编程之IO模型
一 IO模型介绍 为了更好地了解IO模型,我们需要事先回顾下:同步.异步.阻塞.非阻塞 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非 ...
- jq获取兄弟节点
$('#id').siblings() 当前元素所有的兄弟节点$('#id').prev() 当前元素前一个兄弟节点$('#id').prevaAll() 当前元素之前所有的兄弟节点$('#id'). ...
- Hadoop部署方式-高可用集群部署(High Availability)
版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客的高可用集群是建立在完全分布式基础之上的,详情请参考:https://www.cnblogs.com/yinzhengjie/p/90651 ...
- VS生成事件执行XCOPY时出现Invalid num of parameters的解决方案
最近想偷懒 想把项目生成的dll全部自动汇集到一个文件夹下 于是乎就动用了VS的生成后事件 在执行Xcopy的时候碰到了点问题 Invalid number of parameters 挺奇怪的,在公 ...
- SGD中的重要参数
Learning Rate 学习率决定了权值更新的速度,设置得太大会使结果超过最优值,太小会使下降速度过慢.仅靠人为干预调整参数需要不断修改学习率,因此后面3种参数都是基于自适应的思路提出的解决方案. ...
- c# dev GridControl多选当前行显示样式问题
由于Dev GridControl在支持多选的时候,如果如果焦点行单独加了样式,Appearance-->FocusedRow & HideSelectionRow 这个时候,鼠标焦 ...