Go互斥锁和读写锁

本文介绍了Go语言中用于并发控制的两种锁:互斥锁(sync.Mutex)和读写锁(sync.RWMutex)。通过示例展示了在无锁情况下并发修改变量导致的数据不准确问题,以及如何使用互斥锁解决此问题。接着解释了读写锁的工作原理,即读锁之间不互斥,但在写锁存在时读写锁互斥,从而提高并发读取的效率。最后提供了一个读写锁的使用示例。
摘要由CSDN通过智能技术生成

Go互斥锁和读写锁

之前一直有些搞混Go的锁,本文就来学习下Go的两种锁:互斥锁和读写锁

Go语言标准库sync包提供了两种锁类型:sync.Mutex(互斥锁),sync.RWMutex(读写锁)

互斥锁的场景

以下使用示例来演示不加锁的情况:开启三个goroutine,让每个goroutine都对count加1000次1,这样正常情况下最终的count应该是3000

func main() {
	count := 0

	g := sync.WaitGroup{}
	g.Add(3)

	for i := 0; i < 3; i++ {
		go func() {
			defer g.Done()
			for j := 0; j < 1000; j++ {
				count = count + 1
			}
		}()
	}

	g.Wait()

	fmt.Println("count = ", count)
}

执行结果如下图:
在这里插入图片描述

执行了三次,结果却不是3000,而且每一次都不一样,很明显这里产生了问题,就是在count加1时,可能刚读取了count,另一个协程又把count给改了,导致数据不准确,比如可能存在以下场景:

时刻协程1协程2
时刻1读取count为0读取count为0,执行count + 1
时刻2赋值count = 1
时刻3执行count+1并赋值count = 1

这样子就会导致,两个协程都执行了+1,但是结果却还是1,导致最终结果不是3000.

解决方案:加锁

func main() {
	count := 0

	mu := sync.Mutex{}
	g := sync.WaitGroup{}
	g.Add(3)

	for i := 0; i < 3; i++ {
		go func() {
			defer g.Done()
			for j := 0; j < 1000; j++ {
				mu.Lock()
				count = count + 1
				mu.Unlock()
			}
		}()
	}

	g.Wait()

	fmt.Println("count = ", count)
}

count = count + 1前加锁,使得同一时刻只有一个goroutine能读取到count

读写锁

想象下有这种场景,比如银行存钱取钱时,对余额修改是需要加锁的,因为涉及到修改,同一时刻可能有人给你汇钱,同时你也在取钱,就可能导致数据不准确的问题,所以需要加锁,而且读取账户余额时也是需要等待修改操作结束,才能读取到正确的结果的。

然而大部分情况下,读取的操作会比较频繁,如果读取可以并发执行,那效率就会得到很大的提高了,因此只要保证在并发读取的时候没有写的操作,就不会产生问题。

这就是读写锁,读写锁分为了读锁和写锁,写锁肯定是互斥的,但读锁是允许同时执行的(对应了以上场景)

一般来说,读写锁存在以下几种情况:

  1. 读锁之间不互斥,没有写锁的情况下,读锁不阻塞
  2. 写锁之间是互斥的,存在写锁,则其他写锁会阻塞
  3. 写锁和读锁之间是互斥的,存在写锁,则读锁会阻塞;存在读锁,则写锁会阻塞

在读场景比较多时,使用读写锁能有效减少锁阻塞的时间

读写锁示例

var mu sync.RWMutex
var g sync.WaitGroup

func Write(name string) {
	defer g.Done()
	mu.Lock()
	fmt.Printf("%s get write lock...\n", name)
	time.Sleep(1 * time.Second)
	fmt.Printf("%s release write lock...\n\n", name)
	mu.Unlock()
}

func Read(name string) {
	defer g.Done()
	mu.RLock()
	fmt.Printf("%s get read lock...\n", name)
	time.Sleep(1 * time.Second)
	fmt.Printf("%s release write lock...\n\n", name)
	mu.RUnlock()
}

func main() {

	g.Add(4)

	go Write("One")
	go Write("Two")
	go Read("Third")
	go Read("Four")

	g.Wait()
}

执行结果如下(只是其中一种情况):
在这里插入图片描述

通过执行结果可以看出,当第一个协程获取到写锁时,其他所有协程都阻塞等待锁释放,然后第四个协程获取到读锁时(此时没有写锁),其他协程并没有阻塞,所以第三个协程紧跟着也获取到了读锁,然后第三个第四个协程释放了读锁,第二个协程才能获取到写锁(读写锁互斥的)

区别

  1. 互斥锁:互斥的意思就是不可以同时执行,也就是两个代码段互相排斥,只有一个能执行,另一个必须等到锁释放后才可以拿到锁
    sync.Mutex提供了以下两个方法:
  • Lock: 加锁
  • Unlock: 释放锁
  1. 读写锁:读写锁分为了读锁写锁,读锁是运行同时执行的,但写锁是互斥的(很好理解,同一时间多个读取是可以的,但是多个写就不行了,会导致数据错乱)
    sync.RWMutex提供了以下四个方法:
  • Lock: 加写锁
  • Unlock: 释放写锁
  • RLock: 加读锁
  • RUnlock: 释放读锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值