同步之sync.Mutex互斥鎖緩存
sync包中定義了Locker結構來表明鎖。ide
type Locker interface { Lock() Unlock() }
而且定義了兩個結構來實現Locker接口:Mutex 和 RWMutex。函數
咱們能夠用一個容量只有1的channel來保證最多隻有一個goroutine在同一時刻訪問一個共享變量。一個只能爲1和0的信號量叫作二元信號量(binary semaphore)。使用二元信號量實現互斥鎖,示例以下,ui
var ( // 一個二元信號量 // 緩存爲 1 的channel sema = make(chan struct{}, 1) // a binary semaphore guarding balance balance int ) func Deposit(amount int) { // 存錢的時候須要獲取一個信號量,以此來保護變量 balance sema <- struct{}{} // acquire token balance = balance + amount <-sema // release token } func Balance() int { sema <- struct{}{} // acquire token b := balance <-sema // release token return b }
使用互斥鎖sync.Mutex實現示例以下,線程
import ( "sync" ) var ( mu sync.Mutex // guards balance balance int ) func Deposit(amount int) { mu.Lock() balance = balance + amount mu.Unlock() } func Balance() int { mu.Lock() b := balance mu.Unlock() return b }
Go語言裏的sync.Mutex和Java裏的ReentrantLock都實現互斥的語義,但有一個很大的區別就是鎖的可重入性。code
ReentrantLock 意味着什麼呢?簡單來講,它有一個與鎖相關的獲取計數器,若是擁有鎖的某個線程再次獲得鎖,那麼獲取計數器就加1,而後鎖須要被釋放兩次才能得到真正釋放。這模仿了 synchronized 的語義;若是線程進入由線程已經擁有的監控器保護的 synchronized 塊,就容許線程繼續進行,當線程退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。token
ReentrantLock的可重入性用代碼來表示以下,接口
class Parent { protected Lock lock = new ReentrantLock(); public void test() { lock.lock(); try { System.out.println("Parent"); } finally { lock.unlock(); } } } class Sub extends Parent { @Override public void test() { lock.lock(); try { super.test(); System.out.println("Sub"); } finally { lock.unlock(); } } } public class AppTest { public static void main(String[] args) { Sub s = new Sub(); s.test(); } }
要注意到須要作兩次釋放鎖的操做。ci
而Go語言的sync.Mutex互斥鎖沒有可重入的特性,看下面這段代碼,同步
import ( "sync" ) var ( mu sync.Mutex // guards balance balance int ) func Deposit(amount int) { mu.Lock() balance = balance + amount mu.Unlock() } func Balance() int { mu.Lock() b := balance mu.Unlock() return b } // NOTE: incorrect! func Withdraw(amount int) bool { mu.Lock() defer mu.Unlock() Deposit(-amount) if Balance() < 0 { Deposit(amount) return false // insufficient funds } return true }
上面這個例子中,Deposit會調用mu.Lock()第二次去獲取互斥鎖,但由於mutex已經鎖上了,而沒法被重入——也就是說無法對一個已經鎖上的mutex來再次上鎖--這會致使程序死鎖,無法繼續執行下去,Withdraw會永遠阻塞下去。
一個通用的解決方案是將一個函數分離爲多個函數,好比咱們把Deposit分離成兩個:一個不導出的函數deposit,這個函數假設鎖老是會被保持並去作實際的操做,另外一個是導出的函數Deposit,這個函數會調用deposit,但在調用前會先去獲取鎖。同理咱們能夠將Withdraw也表示成這種形式:
func Withdraw(amount int) bool { mu.Lock() defer mu.Unlock() deposit(-amount) if balance < 0 { deposit(amount) return false // insufficient funds } return true } func Deposit(amount int) { mu.Lock() defer mu.Unlock() deposit(amount) } // This function requires that the lock be held. func deposit(amount int) { balance += amount }
=========END=========