Go语言圣经 - 第9章 基于共享变量的并发 - 9.2 sync.Mutex 互斥锁

第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的一个字段

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值