## 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. 相關資源