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