GO语言并发编程之互斥锁、读写锁详解语言并发编程之互斥锁、读写锁详解
在本节,我们对Go语言所提供的与锁有关的API进行说明。这包括了互斥锁和读写锁。我们在第6章描述过互斥锁,但却没有
提到过读写锁。这两种锁对于传统的并发程序来说都是非常常用和重要的。
一、互斥锁一、互斥锁
互斥锁是传统的并发程序对共享资源进行访问控制的主要手段。它由标准库代码包sync中的Mutex结构体类型代表。
sync.Mutex类型(确切地说,是*sync.Mutex类型)只有两个公开方法——Lock和Unlock。顾名思义,前者被用于锁定当前的
互斥量,而后者则被用来对当前的互斥量进行解锁。
类型sync.Mutex的零值表示了未被锁定的互斥量。也就是说,它是一个开箱即用的工具。我们只需对它进行简单声明就可以
正常使用了,就像这样:
代码如下:
var mutex sync.Mutex
mutex.Lock()
在我们使用其他编程语言(比如C或Java)的锁类工具的时候,可能会犯的一个低级错误就是忘记及时解开已被锁住的锁,从
而导致诸如流程执行异常、线程执行停滞甚至程序死锁等等一系列问题的发生。然而,在Go语言中,这个低级错误的发生几
率极低。其主要原因是有defer语句的存在。
我们一般会在锁定互斥锁之后紧接着就用defer语句来保证该互斥锁的及时解锁。请看下面这个函数:
代码如下:
var mutex sync.Mutex
func write() {
mutex.Lock()
defer mutex.Unlock()
// 省略若干条语句
}
函数write中的这条defer语句保证了在该函数被执行结束之前互斥锁mutex一定会被解锁。这省去了我们在所有return语句之前
以及异常发生之时重复的附加解锁操作的工作。在函数的内部执行流程相对复杂的情况下,这个工作量是不容忽视的,并且极
易出现遗漏和导致错误。所以,这里的defer语句总是必要的。在Go语言中,这是很重要的一个惯用法。我们应该养成这种良
好的习惯。
对于同一个互斥锁的锁定操作和解锁操作总是应该成对的出现。如果我们锁定了一个已被锁定的互斥锁,那么进行重复锁定操
作的Goroutine将会被阻塞,直到该互斥锁回到解锁状态。请看下面的示例:
代码如下:
func repeatedlyLock() {
var mutex sync.Mutex
fmt.Println(“Lock the lock. (G0)”)
mutex.Lock()
fmt.Println(“The lock is locked. (G0)”)
for i := 1; i <= 3; i++ {
go func(i int) {
fmt.Printf(“Lock the lock. (G%d)”, i)
mutex.Lock()
fmt.Printf(“The lock is locked. (G%d)”, i)
}(i)
}
time.Sleep(time.Second)