## 9.3\. 同步(Synchronization)
### 9.3.1\. 初始化
程序的初始化在一个独立的goroutine中执行。在初始化过程中创建的goroutine将在 第一个用于初始化goroutine执行完成后启动。
如果包p导入了包q,包q的init 初始化函数将在包p的初始化之前执行。
程序的入口函数 main.main 则是在所有的 init 函数执行完成 之后启动。
在任意init函数中新创建的goroutines,将在所有的init 函数完成后执行。
### 9.3.2\. Goroutine的创建
用于启动goroutine的go语句在goroutine之前运行。
例如,下面的程序:
```
var a string;
func f() {
print(a);
}
func hello() {
a = "hello, world";
go f();
}
```
调用hello函数,会在某个时刻打印“hello, world”(有可能是在hello函数返回之后)。
### 9.3.3\. Channel communication 管道通信
用管道通信是两个goroutines之间同步的主要方法。在管道上执行的发送操作会关联到该管道的 接收操作,这通常对应goroutines。
管道上的发送操作发生在管道的接收完成之前(happens before)。
例如这个程序:
```
var c = make(chan int, 10)
var a string
func f() {
a = "hello, world";
c <- 0;
}
func main() {
go f();
<-c;
print(a);
}
```
可以确保会输出"hello, world"。因为,a的赋值发生在向管道 c发送数据之前,而管道的发送操作在管道接收完成之前发生。 因此,在print 的时候,a已经被赋值。
从一个unbuffered管道接收数据在向管道发送数据完成之前发送。
下面的是示例程序:
```
var c = make(chan int)
var a string
func f() {
a = "hello, world";
<-c;
}
func main() {
go f();
c <- 0;
print(a);
}
```
同样可以确保输出“hello, world”。因为,a的赋值在从管道接收数据 前发生,而从管道接收数据操作在向unbuffered 管道发送完成之前发生。所以,在print 的时候,a已经被赋值。
如果用的是缓冲管道(如 c = make(chan int, 1) ),将不能保证输出 “hello, world”结果(可能会是空字符串, 但肯定不会是他未知的字符串, 或导致程序崩溃)。
### 9.3.4\. 锁
包sync实现了两种类型的锁: sync.Mutex 和 sync.RWMutex。
对于任意 sync.Mutex 或 sync.RWMutex 变量l。 如果 n < m ,那么第n次 l.Unlock() 调用在第 m次 l.Lock() 调用返回前发生。
例如程序:
```
var l sync.Mutex
var a string
func f() {
a = "hello, world";
l.Unlock();
}
func main() {
l.Lock();
go f();
l.Lock();
print(a);
}
```
可以确保输出“hello, world”结果。因为,第一次 l.Unlock() 调用(在f函数中)在第二次 l.Lock() 调用 (在main 函数中)返回之前发生,也就是在 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 the n'th call to l.Unlock and the matching l.RUnlock happens before the n+1'th call to l.Lock.
### 9.3.5\. Once
包once提供了一个在多个goroutines中进行初始化的方法。多个goroutines可以 通过 once.Do(f) 方式调用f函数。 但是,f函数 只会被执行一次,其他的调用将被阻塞直到唯一执行的f()返回。
once.Do(f) 中唯一执行的f()发生在所有的 once.Do(f) 返回之前。
有代码:
```
var a string
func setup() {
a = "hello, world";
}
func doprint() {
once.Do(setup);
print(a);
}
func twoprint() {
go doprint();
go doprint();
}
```
调用twoprint会输出“hello, world”两次。第一次twoprint 函数会运行setup唯一一次。
- 1. 关于本文
- 2. Go语言简介
- 3. 安装go环境
- 3.1. 简介
- 3.2. 安装C语言工具
- 3.3. 安装Mercurial
- 3.4. 获取代码
- 3.5. 安装Go
- 3.6. 编写程序
- 3.7. 进一步学习
- 3.8. 更新go到新版本
- 3.9. 社区资源
- 3.10. 环境变量
- 4. Go语言入门
- 4.1. 简介
- 4.2. Hello,世界
- 4.3. 分号(Semicolons)
- 4.4. 编译
- 4.5. Echo
- 4.6. 类型简介
- 4.7. 申请内存
- 4.8. 常量
- 4.9. I/O包
- 4.10. Rotting cats
- 4.11. Sorting
- 4.12. 打印输出
- 4.13. 生成素数
- 4.14. Multiplexing
- 5. Effective Go
- 5.1. 简介
- 5.2. 格式化
- 5.3. 注释
- 5.4. 命名
- 5.5. 分号
- 5.6. 控制流
- 5.7. 函数
- 5.8. 数据
- 5.9. 初始化
- 5.10. 方法
- 5.11. 接口和其他类型
- 5.12. 内置
- 5.13. 并发
- 5.14. 错误处理
- 5.15. Web服务器
- 6. 如何编写Go程序
- 6.1. 简介
- 6.2. 社区资源
- 6.3. 新建一个包
- 6.4. 测试
- 6.5. 一个带测试的演示包
- 7. Codelab: 编写Web程序
- 7.1. 简介
- 7.2. 开始
- 7.3. 数据结构
- 7.4. 使用http包
- 7.5. 基于http提供wiki页面
- 7.6. 编辑页面
- 7.7. template包
- 7.8. 处理不存在的页面
- 7.9. 储存页面
- 7.10. 错误处理
- 7.11. 模板缓存
- 7.12. 验证
- 7.13. 函数文本和闭包
- 7.14. 试试!
- 7.15. 其他任务
- 8. 针对C++程序员指南
- 8.1. 概念差异
- 8.2. 语法
- 8.3. 常量
- 8.4. Slices(切片)
- 8.5. 构造值对象
- 8.6. Interfaces(接口)
- 8.7. Goroutines
- 8.8. Channels(管道)
- 9. 内存模型
- 9.1. 简介
- 9.2. Happens Before
- 9.3. 同步(Synchronization)
- 9.4. 错误的同步方式
- 10. 附录
- 10.1. 命令行工具
- 10.2. 视频和讲座
- 10.3. Release History
- 10.4. Go Roadmap
- 10.5. 相关资源