同步之sync.Mutex互斥鎖

同步之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=========

相關文章
相關標籤/搜索