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

相关文章
相关标签/搜索