在进行服务器端开发时,为了提升程序性能,不可避免的要接触到多线程。
Go语言中的锁主要有两种,互斥锁和读写锁
互斥锁的结构体为:
type Mutex struct {
state int32
sema uint32
}
读写锁的结构体为:
type RWMutex struct {
w Mutex //如果有待处理的写操作就持有
writerSem uint32 // 写操作等待读操作完成的信号量
readerSem uint32 //读操作等待写操作完成的信号量
readerCount int32 // 待处理的读操作数量
readerWait int32 // number of departing readers
}

具体的使用方法,本文不过多讨论,网上非常的多资料,本文重在讨论两个锁的区别,性能,以及应用场景。

首先提一个概念:数据竞争
数据竞争指的是程序在多个goroutine交叉执行操作时,没有给出正确的结果。竞争条件是很恶劣的一种场景,因为这种问题会一直潜伏在你的程序里,然后在非常少见的时候蹦出来, 或许只是会在很大的负载时才会发生,又或许是会在使用了某一个编译器、某一种平台或者某一种架构的时候才会出现。这些使得竞争条件带来的问题非常难以复现而且难以分析诊断。

如,每时每刻都会有非常多的人往银行里存钱,取钱,查看余额。但是银行的总资产是一个单一的变量。假如同时有非常多的人对这个变量进行操作,就会因为某些原因,导致数据丢失,遗漏,甚至银行的程序崩溃。

这种情况就被叫做数据竞争。为了避免这种情况,我们要对银行的资产做一些保护,让所有人的操作按顺序执行,于是就出现了锁。

1.互斥锁
传统并发程序对共享资源进行访问控制的主要手段,
注意:

对一个已经上锁的对象再次上锁,那么就会导致该锁定操作被阻塞,直到该互斥锁回到被解锁状态,互斥锁锁定操作的逆操作并不会导致协程阻塞,但是有可能导致引发一个无法恢复的运行时的panic,比如对一个未锁定的互斥锁进行解锁时就会发生panic。避免这种情况的最有效方式就是使用defer。

2.读写锁
读写锁是针对读写操作的互斥锁,可以分别针对读操作与写操作进行锁定和解锁操作 。
读写锁的访问控制规则如下:
① 多个写操作之间是互斥的
② 写操作与读操作之间也是互斥的
③ 多个读操作之间不是互斥的
在这样的控制规则下,读写锁可以大大降低性能损耗。
读写锁的Unlock方法会试图唤醒所有因欲进行读锁定而被阻塞的协程,而 RUnlock 只会在已无任何读锁定的情况下,试图唤醒一个因欲进行写锁定而被阻塞的协程。若对一个未被写锁定的读写锁进行写解锁,就会引发一个不可恢复的panic,同理对一个未被读锁定的读写锁进行读写锁也会如此。
由于读写锁控制下的多个读操作之间不是互斥的,因此对于读解锁更容易被忽视。对于同一个读写锁,添加多少个读锁定,就必要有等量的读解锁,这样才能其他协程有机会进行操作。

性能差距:

  1. 如果对某一共享数据的大多数操作都是读,小部分操作是写的情况下,读写锁的性能要优于互斥锁
  2. 如果是一般情况,或者写的操作大于读的操作,互斥锁的性能会优于读写锁,因为读写锁需要更复杂的内部记录
Last modification:June 14th, 2019 at 03:36 pm
如果觉得我的文章对你有用,请随意赞赏