Go 语言系列28:互斥锁与读写锁
在 Go 语言中,经常会遇到并发的问题,当然我们会优先考虑使用信道,但在这里我们会讨论如何通过 Mutex(互斥锁) 和 RWMutex(读写锁) 来处理竞争条件。
临界区
首先我们要理解并发编程中临界区的概念。当程序并发地运行时,多个 Go 协程不应该同时访问那些修改共享资源的代码。这些修改共享资源的代码称为 临界区 。
我们这里举一个简单的例子,假如要实现一个功能,使当前变量的值加 100
,我们会写出下面的代码:
当然,对于只有一个协程的程序来说,上面的代码没有任何问题。但是,如果有多个协程并发运行时,就会发生错误,为了简单起见,这里只用两个协程。
第一个协程获取当前 x
的值,并计算 x + 100
,最后将计算后的值赋给 x
。同理,第二个协程也是按照第一个协程的步骤做。
一种可能发生的情况是第一个协程获取到 x
的值,然后对其加上 100
再赋给 x
,此时 x
的值更新为 x + 100
。第一个协程执行完毕后执行第二个协程,获取到 x
的值为 x + 100
,然后再对其加上 100
再赋给 x
,此时 x
的值更新为 x + 200
。最后 x
的结果为 x + 200
。
但是,这里也有可能会发生另一种情况,那就是第一个协程获取完 x
的值后,还没执行完该协程,就切换到第二个协程执行,第二个协程同样获取到 x
的值,然后对 x
的值加 100
,并赋值给 x
,x
更新为 x + 100
。在第二个协程执行完后,又切换到第一个协程执行,此时第一个协程 x
的值还为 x
,对其进行加 100
的操作后, x
的值更新为 x + 100
,再赋值给 x
变量,最后导致执行完两个协程结果变量 x
的值为 x + 100
。
所以,由于上下文切换的不同, x
的值可能更新为 x + 100
或者 x + 200
,这种情况就称之为竞争条件。使用下面的互斥锁 Mutex
就能避免这种情况的发生。
互斥锁 Mutex
互斥锁(Mutex,mutual exclusion) 用于提供一种 加锁机制(Locking Mechanism) ,可确保在某时刻只有一个协程在临界区运行,以防止出现竞争条件。使用其是为了来保护一个资源不会因为并发操作而引起冲突导致数据不准确。
Mutex
的定义有以下两种方法:
或者
Mutex
有两个方法,分别是 Lock
和 Unlock
,即对应的加锁和解锁。在 Lock
和 Unlock
之间的代码,都只能由一个协程执行,就能避免竞争条件。
如果有一个协程已经持有了 锁(Lock) ,当其他协程试图获得该锁时,这些协程会被阻塞,直到 Mutex
解除锁定为止。
下面使用一个例子来讲一讲互斥锁的使用,首先看到下面的代码,使用两个协程,分别计算 x
加上 1000
次 1
,分析代码不难看出,执行完两个协程后 x
的结果应该为 2000
:
但运行完该程序后,输出的结果值每次都不同,而且都不等于 2000
。通过上面的讲解,相信你应该知道为什么会出现这种情况了,为了解决这个问题,我们就要对 add
这个函数加上互斥锁,使同一时刻,只能有一个协程对 x
进行操作:
更改后的代码不管运行多少次,都只会输出一个结果,那就是 2000
。
当然,使用互斥锁很简单,但要注意同一协程里不要在尚未解锁时再次加锁,也不要对已经解锁的锁再次解锁。
当然,使用信道也可以处理竞争条件,把信道作为锁在前面讲信道的时候已经讲过,这里就不再赘述。
读写锁 RWMutex
sync.RWMutex
类型实现读写互斥锁,为了保证数据的安全,它规定了当有人还在读取数据(即读锁占用)时,不允许有人更新这个数据(即写锁会阻塞);为了保证程序的效率,多个人(线程)读取数据(拥有读锁)时,互不影响不会造成阻塞,它不会像 Mutex
那样只允许有一个人(线程)读取同一个数据。
RWMutex
是基于 Mutex
的,在 Mutex
的基础之上增加了读、写的信号量,并使用了类似引用计数的读锁数量。读锁与读锁兼容,读锁与写锁互斥,写锁与写锁互斥,只有在锁释放后才可以继续申请互斥的锁:
- 可以同时申请多个读锁;
- 有读锁时申请写锁将阻塞,有写锁时申请读锁将阻塞;
- 只要有写锁,后续申请读锁和写锁都将阻塞。
定义一个 RWMuteux
读写锁,同样有两种方法:
或者
RWMutex
里提供了两种锁,每种锁分别对应两个方法,为了避免死锁,两个方法应成对出现,必要时请使用 defer
。
- 读锁:调用
RLock
方法开启锁,调用 RUnlock
释放锁; - 写锁:调用
Lock
方法开启锁,调用 Unlock
释放锁。
下面是摘自网络上的一个例子:
运行输出如下:
参考文献:
[1] Alan A. A. Donovan; Brian W. Kernighan, Go 程序设计语言, Translated by 李道兵, 高博, 庞向才, 金鑫鑫 and 林齐斌, 机械工业出版社, 2017.