make loop

This commit is contained in:
chai2010
2016-01-18 09:14:09 +08:00
parent 8116733b0c
commit 324d2c8925

View File

@@ -1,6 +1,6 @@
## 9.2. sync.Mutex互斥鎖
在8.6中,我使用了一buffered channel作为一个计数信号量,来保证最多有20goroutine会同时执行HTTP求。同理,我可以用一容量有1的channel来保证最多有一goroutine在同一时刻访问一个共享量。一个只能为1和0的信量叫做二元信量(binary semaphore)。
在8.6中,我使用了一buffered channel作爲一個計數信號量,來保證最多有20goroutine會同時執行HTTP求。同理,我可以用一容量有1的channel來保證最多有一goroutine在同一時刻訪問一個共享量。一個隻能爲1和0的信量叫做二元信量(binary semaphore)。
```go
gopl.io/ch9/bank2
@@ -23,7 +23,7 @@ func Balance() int {
}
```
这种互斥很而且被sync包里的Mutex型直接支持。它的Lock方法能够获取到token(里叫)且Unlock方法会释放这个token
這種互斥很而且被sync包里的Mutex型直接支持。它的Lock方法能夠獲取到token(里叫)且Unlock方法會釋放這個token
```go
gopl.io/ch9/bank3
@@ -48,13 +48,13 @@ func Balance() int {
}
```
每次一goroutine访问bank变量时(这里只有balance余额变量),它都会调用mutex的Lock方法来获取一互斥。如果其它的goroutine已经获得了这个锁的话,这个操作被阻塞直到其它goroutine用了Unlock使该锁变回可用状态。mutex会保护共享量。惯例来说被mutex所保护的变量是在mutex变量声明之立刻明的。如果你的做法和例不符,保在文档里对你的做法进行说明。
每次一goroutine訪問bank變量時(這里隻有balance餘額變量),它都會調用mutex的Lock方法來獲取一互斥。如果其它的goroutine已經獲得了這個鎖的話,這個操作被阻塞直到其它goroutine調用了Unlock使該鎖變迴可用狀態。mutex會保護共享量。慣例來説被mutex所保護的變量是在mutex變量聲明之立刻明的。如果你的做法和例不符,保在文檔里對你的做法進行説明。
在Lock和Unlock之的代段中的容goroutine可以随便读取或者改,这个代码段叫做临界区。goroutine在结束后释放锁是必要的,无论以哪条路径通过函数都需要放,使是在错误路径中,也要记得释放。
在Lock和Unlock之的代段中的容goroutine可以隨便讀取或者改,這個代碼段叫做臨界區。goroutine在結束後釋放鎖是必要的,無論以哪條路徑通過函數都需要放,使是在錯誤路徑中,也要記得釋放。
上面的bank程序例了一通用的并发模式。一列的出函数封装了一或多个变量,那么访问这些变量唯一的方式就是通过这些函数来做(或者方法,对于一个对象的变量来说)。每一个函数在一始就取互斥锁并在最后释放锁,从而保共享量不会被并发访问。这种函数、互斥锁和变量的排叫作控monitor(这种老式单词的monitor是受"monitor goroutine"的术语启发而来的。两种用法都是一代理人保证变量被顺序访问)。
上面的bank程序例了一通用的併發模式。一列的出函數封裝了一或多個變量,那麽訪問這些變量唯一的方式就是通過這些函數來做(或者方法,對於一個對象的變量來説)。每一個函數在一始就取互斥鎖併在最後釋放鎖,從而保共享量不會被併發訪問。這種函數、互斥鎖和變量的排叫作控monitor(這種老式單詞的monitor是受"monitor goroutine"的術語啟發而來的。兩種用法都是一代理人保證變量被順序訪問)。
在存款和查询余额函数中的临界区代码这么短--有一行,有分支用--在代码最后去调用Unlock就得更直截了。在更复杂的临界区的应用中,尤其是必须要尽早处理错误并返回的情下,就很去(靠人)判断对Lock和Unlock的用是在所有路中都能够严格配的了。Go言里的defer直就是这种情况下的救星:我用defer来调用Unlock临界区会隐式地延伸到函作用域的最后,这样我们就从“总要记得在函数返回之后或者发生错误返回时要记得调用一次Unlock”这种状态中获得了解放。Go会自动帮我们完成些事情。
在存款和査詢餘額函數中的臨界區代碼這麽短--有一行,有分支調用--在代碼最後去調用Unlock就得更直截了。在更複雜的臨界區的應用中,尤其是必鬚要盡早處理錯誤併返迴的情下,就很去(靠人)判斷對Lock和Unlock的調用是在所有路中都能夠嚴格配的了。Go言里的defer直就是這種情況下的救星:我用defer來調用Unlock臨界區會隱式地延伸到函作用域的最後,這樣我們就從“總要記得在函數返迴之後或者發生錯誤返迴時要記得調用一次Unlock”這種狀態中獲得了解放。Go會自動幫我們完成些事情。
```go
@@ -65,11 +65,11 @@ func Balance() int {
}
```
上面的例子里Unlock在return语句读取完balance的值之后执所以Balance函数是并发安全的。这带来的另一点好处是,我再也不需要一本地量b了。
上面的例子里Unlock在return語句讀取完balance的值之後執所以Balance函數是併發安全的。這帶來的另一點好處是,我再也不需要一本地量b了。
此外,一deferred Unlock使在临界区发生panic依然会执行,这对于用recover (§5.10)来恢复的程序来说是很重要的。defer调用只会比显式地用Unlock成本高那么一点点,不过却在很大程度上保了代的整性。大多数情况下对于并发程序来说,代的整性比度的化更重要。如果可能的话尽量使用defer来将临界区扩展到函数的结束。
此外,一deferred Unlock使在臨界區發生panic依然會執行,這對於用recover (§5.10)來恢複的程序來説是很重要的。defer調用隻會比顯式地調用Unlock成本高那麽一點點,不過卻在很大程度上保了代的整性。大多數情況下對於併發程序來説,代的整性比度的化更重要。如果可能的話盡量使用defer來將臨界區擴展到函數的結束。
一下下面的Withdraw函。成功的候,它会正确地减掉余额并返回true。但如果银行记录资金对交易来说不足,那取款就会恢复余额,并返回false。
一下下面的Withdraw函。成功的候,它會正確地減掉餘額併返迴true。但如果銀行記録資金對交易來説不足,那取款就會恢複餘額,併返迴false。
```go
// NOTE: not atomic!
@@ -83,9 +83,9 @@ func Withdraw(amount int) bool {
}
```
数终于给出了正确的结果,但是有一点讨厌的副作用。当过多的取款操作同时执行时balance可能会瞬时被减到0以下。可能引起一个并发的取款被不合逻辑地拒。所以如果Bob尝试买一辆sports carAlice可能就没办法为她的早咖啡付款了。里的问题是取款不是一原子操作:它包含了三个步骤,每一步都需要去获取并释放互斥,但任何一次都不会锁上整取款流程。
數終於給出了正確的結果,但是有一點討厭的副作用。當過多的取款操作同時執行時balance可能會瞬時被減到0以下。可能引起一個併發的取款被不合邏輯地拒。所以如果Bob嚐試買一輛sports carAlice可能就沒辦法爲她的早咖啡付款了。里的問題是取款不是一原子操作:它包含了三個步驟,每一步都需要去獲取併釋放互斥,但任何一次都不會鎖上整取款流程。
理想情下,取款应该只在整操作中得一次互斥。下面这样的尝试是错误的:
理想情下,取款應該隻在整操作中得一次互斥。下面這樣的嚐試是錯誤的:
```go
// NOTE: incorrect!
@@ -101,11 +101,11 @@ func Withdraw(amount int) bool {
}
```
上面这个例子中Deposit会调用mu.Lock()第二次去取互斥,但因mutex已经锁上了,而法被重入(译注go里有重入锁,关于重入的概念,请参考java)--也就是说没法对一个已经锁上的mutex再次上--这会导致程序死锁,没法继续执行下去Withdraw会永远阻塞下去。
上面這個例子中Deposit會調用mu.Lock()第二次去取互斥,但因mutex已經鎖上了,而法被重入(譯註go里有重入鎖,關於重入的概念,請參考java)--也就是説沒法對一個已經鎖上的mutex再次上--這會導致程序死鎖,沒法繼續執行下去Withdraw會永遠阻塞下去。
关于Go的互斥量不能重入这一点我们有很充分的理由。互斥量的目的是为了确保共享量在程序执行时的关键点上能够保证不变性。不性的其中之一是“有goroutine访问共享量”。但实际上对于mutex保护的变量来说,不变性还包括其它方面。当一个goroutine得了一互斥锁时,它会断定这种不变性能被保持。其获取并保持锁期间,可能去更新共享量,这样不变性只是短地被破。然而当其释放锁之后,它必须保证不变性已经恢复原样。尽管一可以重入的mutex也可以保证没有其它的goroutine在访问共享量,但这种方式法保证这些变量额外的不性。(译注:这段翻译有点晕)
關於Go的互斥量不能重入這一點我們有很充分的理由。互斥量的目的是爲了確保共享量在程序執行時的關鍵點上能夠保證不變性。不性的其中之一是“有goroutine訪問共享量”。但實際上對於mutex保護的變量來説,不變性還包括其它方面。當一個goroutine得了一互斥鎖時,它會斷定這種不變性能被保持。其獲取併保持鎖期間,可能去更新共享量,這樣不變性隻是短地被破。然而當其釋放鎖之後,它必鬚保證不變性已經恢複原樣。盡管一可以重入的mutex也可以保證沒有其它的goroutine在訪問共享量,但這種方式法保證這些變量額外的不性。(譯註:這段翻譯有點暈)
通用的解方案是将一个函数分离为多个函数,比如我把Deposit分离成两个:一个不导出的函deposit这个函数假设锁总是会被保持去做实际的操作,另一个是导出的函Deposit这个函数会调用deposit但在用前先去获取锁。同理我可以Withdraw也表示成这种形式:
通用的解方案是將一個函數分離爲多個函數,比如我把Deposit分離成兩個:一個不導出的函deposit這個函數假設鎖總是會被保持去做實際的操作,另一個是導出的函Deposit這個函數會調用deposit但在調用前先去獲取鎖。同理我可以Withdraw也表示成這種形式:
```go
func Withdraw(amount int) bool {
@@ -136,8 +136,8 @@ func deposit(amount int) { balance += amount }
```
然,里的存款deposit函很小实际上取款withdraw函不需要理会对它的用,管如此,里的表达还是表明了规则
然,里的存款deposit函很小實際上取款withdraw函不需要理會對它的調用,管如此,里的表達還是表明了規則
(§6.6), 用限制一个程序中的意外交互的方式,可以使我们获得数据结构的不性。因为某种原因,封装还帮我们获得了并发的不性。你使用mutex时,确保mutex和其保护的变量没有被出(在go里也就是小,且不要被大字母开头的函数访问啦),无论这些变量是包级的变量还是一struct的字段。
(§6.6), 用限製一個程序中的意外交互的方式,可以使我們獲得數據結構的不性。因爲某種原因,封裝還幫我們獲得了併發的不性。你使用mutex時,確保mutex和其保護的變量沒有被出(在go里也就是小,且不要被大字母開頭的函數訪問啦),無論這些變量是包級的變量還是一struct的字段。