第9章 基于共享变量的并发
前一章我们使用了goroutine和channel这样直接而自然的方式来实现并发的方法,有时候它们会存在一些问题
本章我们会更详细的介绍并发机制,尤其在goroutine之间共享变量,我们还会介绍goroutine和操作系统线程之间一些技术差别
9.2 sync.Mutex 互斥锁
我们之前使用了有缓存的channel来限制同时最多只有20个goroutine来处理HTTP请求,同理,我们可以使用只有一个缓存的channel来保证同时只有一个goroutine来访问变量,一个只能为1或0的信号量叫做二元信号量
我们来看一下实现这个过程的程序,可以看到我们在访问某个变量之前会先通过channel获取一个信号,访问完之后我们随即把信息release
package bank
var (
sema = make(chan struct{},1)
balance int
)
func Deposit(amount int) {
sema <- struct{}{}
balance = balance +amount
<-sema
}
func Balance() int {
sema <- struct{}{}
b := balance
<-sema
return b
}
这种机制非常有用,它被sync包中的Mutex类型直接支持,它的lock和unlock分别能够获取和释放锁,我们再来用这个类型重新写一下上述程序
package bank
import "sync"
var (
mu sync.Mutex
balance int
)
func Deposit(amount int) {
mu.Lock()
balance = balance + amount
}
func Balance() int {
mu.Lock()
b := balance
mu.Unlock()
return b
}
lock和unlock之间的代码段中的内容可以随意修改或者读取,这个代码段叫做临界区。关于unlock的使用,在复杂的临界区应用中,我们希望尽早处理并返回会错误,这时候可以使用defer语句,这时候临界区会隐式的延伸到函数作用域的最后,这时候go会帮我们完成,并且像下面的程序一样,我们也不再需要本地变量b了
func Balance() int {
mu.Lock()
defer mu.Unlock()
return balance
}
一个deferred Unlock即使在临界区发生panic时依然会执行,这对于用recover来回复函数来说非常重要,defer的调用成本会高一点点的,但是它极大的提高了代码的整洁性,这是很重要的一点,所哟如果可能的话建议尽可能使用defer来扩展临界区直到函数结束
func withdraw(amount int) bool {
Deposit(-amount)
if Balance() < 0 {
Deposit(amount)
return false
}
return true
}
我们再来看一下上述函数,当我们取钱时,函数会先判断余额是否为0,如果为零的话,就恢复余额,如果不为零,就从余额中扣除需要取出的金额,取钱操作顺利执行完成
理想情况下,取款应该在整个程序中获得依次互斥锁,如下的程序是错误的,因为我们可能会在余额不足的情况下两次尝试修改变量,但是事实上第一次我们已经上锁了,并且并没有释放
func withdraw(amount int) bool {
mu.Lock()
defer mu.Unlock()
Deposit(-amount)
if Balance() < 0 {
Deposit(amount)
return false
}
return true
}
那业务逻辑就是这样的,虽然它不符合go语言本身的这个互斥锁逻辑,我们该怎么办呢,一个好的方法就是把它拆分为不同的函数,也就是说锁也可以套娃使用的
func withdraw(amount int) bool {
mu.Lock()
defer mu.Unlock()
deposit(-amount)
if Balance() < 0 {
deposit(amount)
return false
}
return true
}
func Deposit(amount int) {
mu.Lock()
defer mu.Unlock()
deposit(amount)
}
func deposit(amount int) {balance += amount}
封装,用限制一个程序中的意外交互方式,可以使我们获得数据结构的不变性,由于一些原因,它还让我们获得了并发的不变性,上述程序就是(从多个角度去理解)。当我们使用mutex时,确保mutex和其保护的变量没有被导出,无论这个变量是包级别的变量还是一个struct的一个字段