update progress.md & make loop

This commit is contained in:
chai2010 2016-01-11 10:05:57 +08:00
parent 8fc11cf402
commit e74f2527fc
2 changed files with 50 additions and 50 deletions

View File

@ -1,49 +1,49 @@
## 9.5. sync.Once初始化 ## 9.5. sync.Once初始化
如果初始化成本比较大的话那么将初始化延迟到需要的时候再去做就是一个比较好的选择。如果在程序启动的时候就去做这类的初始化的话会增加程序的启动时间并且因为执行的时候可能也并不需要这些变量所以实际上有一些浪费。让我们在本章早一些时候看到的icons变量: 如果初始化成本比較大的話那麽將初始化延遲到需要的時候再去做就是一個比較好的選擇。如果在程序啟動的時候就去做這類的初始化的話會增加程序的啟動時間併且因爲執行的時候可能也併不需要這些變量所以實際上有一些浪費。讓我們在本章早一些時候看到的icons變量:
```go ```go
var icons map[string]image.Image var icons map[string]image.Image
``` ```
这个版本的Icon用到了懒初始化(lazy initialization)。 這個版本的Icon用到了懶初始化(lazy initialization)。
```go ```go
func loadIcons() { func loadIcons() {
icons = map[string]image.Image{ icons = map[string]image.Image{
"spades.png": loadIcon("spades.png"), "spades.png": loadIcon("spades.png"),
"hearts.png": loadIcon("hearts.png"), "hearts.png": loadIcon("hearts.png"),
"diamonds.png": loadIcon("diamonds.png"), "diamonds.png": loadIcon("diamonds.png"),
"clubs.png": loadIcon("clubs.png"), "clubs.png": loadIcon("clubs.png"),
} }
} }
// NOTE: not concurrency-safe! // NOTE: not concurrency-safe!
func Icon(name string) image.Image { func Icon(name string) image.Image {
if icons == nil { if icons == nil {
loadIcons() // one-time initialization loadIcons() // one-time initialization
} }
return icons[name] return icons[name]
} }
``` ```
如果一个变量只被一个单独的goroutine所访问的话我们可以使用上面的这种模板但这种模板在Icon被并发调用时并不安全。就像前面银行的那个Deposit(存款)函数一样Icon函数也是由多个步骤组成的首先测试icons是否为空然后load这些icons之后将icons更新为一个非空的值。直觉会告诉我们最差的情况是loadIcons函数被多次访问会带来数据竞争。当第一个goroutine在忙着loading这些icons的时候另一个goroutine进入了Icon函数发现变量是nil然后也会调用loadIcons函数 如果一個變量隻被一個單獨的goroutine所訪問的話我們可以使用上面的這種模闆但這種模闆在Icon被併發調用時併不安全。就像前面銀行的那個Deposit(存款)函數一樣Icon函數也是由多個步驟組成的首先測試icons是否爲空然後load這些icons之後將icons更新爲一個非空的值。直覺會告訴我們最差的情況是loadIcons函數被多次訪問會帶來數據競爭。當第一個goroutine在忙着loading這些icons的時候另一個goroutine進入了Icon函數發現變量是nil然後也會調用loadIcons函數
过这种直觉是错误的。(我们希望现在你从现在开始能够构建自己对并发的直觉,也就是说对并发的直觉总是不能被信任的!)回忆一下9.4节。因为缺少显式的同步编译器和CPU是可以随意地去更改访问内存的指令顺序以任意方式只要保证每一个goroutine自己的执行顺序一致。其中一种可能loadIcons的语句重排是下面这样。它会在填写icons变量的值之前先用一个空map来初始化icons变量。 過這種直覺是錯誤的。(我們希望現在你從現在開始能夠構建自己對併發的直覺,也就是説對併發的直覺總是不能被信任的!)迴憶一下9.4節。因爲缺少顯式的同步編譯器和CPU是可以隨意地去更改訪問內存的指令順序以任意方式隻要保證每一個goroutine自己的執行順序一致。其中一種可能loadIcons的語句重排是下面這樣。它會在填寫icons變量的值之前先用一個空map來初始化icons變量。
```go ```go
func loadIcons() { func loadIcons() {
icons = make(map[string]image.Image) icons = make(map[string]image.Image)
icons["spades.png"] = loadIcon("spades.png") icons["spades.png"] = loadIcon("spades.png")
icons["hearts.png"] = loadIcon("hearts.png") icons["hearts.png"] = loadIcon("hearts.png")
icons["diamonds.png"] = loadIcon("diamonds.png") icons["diamonds.png"] = loadIcon("diamonds.png")
icons["clubs.png"] = loadIcon("clubs.png") icons["clubs.png"] = loadIcon("clubs.png")
} }
``` ```
因此,一个goroutine在检查icons是非空时也并不能就假设这个变量的初始化流程已经走完了(译注可能只是塞了个空map里面的值还没填完也就是说填值的语句都没执行完呢)。 因此,一個goroutine在檢査icons是非空時也併不能就假設這個變量的初始化流程已經走完了(譯註可能隻是塞了個空map里面的值還沒填完也就是説填值的語句都沒執行完呢)。
简单且正确的保证所有goroutine能够观察到loadIcons效果的方式是用一个mutex来同步检查 簡單且正確的保證所有goroutine能夠觀察到loadIcons效果的方式是用一個mutex來同步檢査
```go ```go
var mu sync.Mutex // guards icons var mu sync.Mutex // guards icons
@ -51,56 +51,56 @@ var icons map[string]image.Image
// Concurrency-safe. // Concurrency-safe.
func Icon(name string) image.Image { func Icon(name string) image.Image {
mu.Lock() mu.Lock()
defer mu.Unlock() defer mu.Unlock()
if icons == nil { if icons == nil {
loadIcons() loadIcons()
} }
return icons[name] return icons[name]
} }
``` ```
然而使用互斥访问icons的代价就是没有办法对该变量进行并发访问即使变量已经被初始化完毕且再也不会进行变动。这里我们可以引入一个允许多读的锁 然而使用互斥訪問icons的代價就是沒有辦法對該變量進行併發訪問卽使變量已經被初始化完畢且再也不會進行變動。這里我們可以引入一個允許多讀的鎖
```go ```go
var mu sync.RWMutex // guards icons var mu sync.RWMutex // guards icons
var icons map[string]image.Image var icons map[string]image.Image
// Concurrency-safe. // Concurrency-safe.
func Icon(name string) image.Image { func Icon(name string) image.Image {
mu.RLock() mu.RLock()
if icons != nil { if icons != nil {
icon := icons[name] icon := icons[name]
mu.RUnlock() mu.RUnlock()
return icon return icon
} }
mu.RUnlock() mu.RUnlock()
// acquire an exclusive lock // acquire an exclusive lock
mu.Lock() mu.Lock()
if icons == nil { // NOTE: must recheck for nil if icons == nil { // NOTE: must recheck for nil
loadIcons() loadIcons()
} }
icon := icons[name] icon := icons[name]
mu.Unlock() mu.Unlock()
return icon return icon
} }
``` ```
上面的代码有两个临界区。goroutine首先会获取一个写锁查询map然后释放锁。如果条目被找到了(一般情况下)那么会直接返回。如果没有找到那goroutine会获取一个写锁。不释放共享锁的话也没有任何办法来将一个共享锁升级为一个互斥锁所以我们必须重新检查icons变量是否为nil以防止在执行这一段代码的时候icons变量已经被其它gorouine初始化过了。 上面的代碼有兩個臨界區。goroutine首先會獲取一個寫鎖査詢map然後釋放鎖。如果條目被找到了(一般情況下)那麽會直接返迴。如果沒有找到那goroutine會獲取一個寫鎖。不釋放共享鎖的話也沒有任何辦法來將一個共享鎖陞級爲一個互斥鎖所以我們必鬚重新檢査icons變量是否爲nil以防止在執行這一段代碼的時候icons變量已經被其它gorouine初始化過了。
上面的模板使我们的程序能够更好的并发但是有一点太复杂且容易出错。幸运的是sync包为我们提供了一个专门的方案来解决这种一次性初始化的问题sync.Once。概念上来讲一次性的初始化需要一个互斥量mutex和一个boolean变量来记录初始化是不是已经完成了互斥量用来保护boolean变量和客户端数据结构。Do这个唯一的方法需要接收初始化函数作为其参数。让我们用sync.Once来简化前面的Icon函数吧: 上面的模闆使我們的程序能夠更好的併發但是有一點太複雜且容易出錯。幸運的是sync包爲我們提供了一個專門的方案來解決這種一次性初始化的問題sync.Once。概念上來講一次性的初始化需要一個互斥量mutex和一個boolean變量來記録初始化是不是已經完成了互斥量用來保護boolean變量和客戶端數據結構。Do這個唯一的方法需要接收初始化函數作爲其參數。讓我們用sync.Once來簡化前面的Icon函數吧:
```go ```go
var loadIconsOnce sync.Once var loadIconsOnce sync.Once
var icons map[string]image.Image var icons map[string]image.Image
// Concurrency-safe. // Concurrency-safe.
func Icon(name string) image.Image { func Icon(name string) image.Image {
loadIconsOnce.Do(loadIcons) loadIconsOnce.Do(loadIcons)
return icons[name] return icons[name]
} }
``` ```
每一次对Do(loadIcons)的调用都会锁定mutex并会检查boolean变量。在第一次调用时变量的值是falseDo会调用loadIcons并会将boolean设置为true。随后的调用什么都不会做但是mutex同步会保证loadIcons对内存(这里其实就是指icons变量啦)产生的效果能够对所有goroutine可见。用这种方式来使用sync.Once的话我们能够避免在变量被构建完成之前和其它goroutine共享该变量。 每一次對Do(loadIcons)的調用都會鎖定mutex併會檢査boolean變量。在第一次調用時變量的值是falseDo會調用loadIcons併會將boolean設置爲true。隨後的調用什麽都不會做但是mutex同步會保證loadIcons對內存(這里其實就是指icons變量啦)産生的效果能夠對所有goroutine可見。用這種方式來使用sync.Once的話我們能夠避免在變量被構建完成之前和其它goroutine共享該變量。
练习 9.2: 重写2.6.2节中的PopCount的例子使用sync.Once只在第一次需要用到的时候进行初始化。(虽然实际上对PopCount这样很小且高度优化的函数进行同步可能代价没法接受) **練習 9.2** 重寫2.6.2節中的PopCount的例子使用sync.Once隻在第一次需要用到的時候進行初始化。(雖然實際上對PopCount這樣很小且高度優化的函數進行同步可能代價沒法接受)

View File

@ -82,7 +82,7 @@
- [ ] 9.2 Mutual Exclusion: sync.Mutex - [ ] 9.2 Mutual Exclusion: sync.Mutex
- [x] 9.3 Read/Write Mutexes: sync.RWMutex - [x] 9.3 Read/Write Mutexes: sync.RWMutex
- [x] 9.4 Memory Synchronization - [x] 9.4 Memory Synchronization
- [ ] 9.5 Lazy Initialization: sync.Once - [x] 9.5 Lazy Initialization: sync.Once
- [x] 9.6 The Race Detector - [x] 9.6 The Race Detector
- [ ] 9.7 Example: Concurrent Non-Blocking Cache - [ ] 9.7 Example: Concurrent Non-Blocking Cache
- [x] 9.8 Goroutines and Threads - [x] 9.8 Goroutines and Threads