锁,共享锁,排他锁,死锁介绍

我被代码海扁 @codebeatme-s @103466775
阅读 4:55·字数 1475·发布 

锁是一种并行状态下确保业务逻辑正常运转的机制,其出现的根本原因是并行带来的执行顺序的不确定性,当同一个资源被施加了顺序不确定的代码逻辑后,出现错误将是可以预期的。在不同的系统或语言环境中,锁的规则和具体实现会有所不同,这主要取决于需要对资源进行何种形式和程度的访问控制。

虽然规则和具体实现不会完全一致,但锁的使用流程是基本相同的,在某些操作执行之前,必须使用一种令牌请求相关锁,只有成功获取,操作才能继续,当操作完成时,持有的锁将被释放。

锁和多线程之间的关系
如上所述,锁之所以被使用是由于对共享资源的并行访问,因此锁和多线程并没有必然的联系,虽然他是锁常见的应用场景。当你以某种方式实现了单线程中某一资源的并行访问时,锁也可以被用在单线程中。

共享锁

共享锁允许被多个目标同时持有,是拒绝授予排他锁的依据,当某个令牌已被用于共享锁时,使用该令牌申请排他锁将是不可行的。

由于共享锁可以被共同持有,因此其对应的业务逻辑一般不包含对同一资源的写入操作,否则可能会造成信息的错乱。

排他锁

排他锁只允许被一个目标持有,是拒绝授予共享锁或另一个排他锁的依据,当某个令牌被用于排他锁后,不能再使用该令牌申请共享锁或另一个排他锁。

由于排他锁仅允许单独持有,因此在其对应的业务逻辑中执行写入操作是安全的,当然,前提是同一资源的所有写入操作均运用了锁机制。

在下面的 C# 代码中,我们将变量locker作为令牌,并使用排他锁限制变量balance的访问,当新的线程通过函数Pay修改balance时,主线程调用的函数GetBalance将陷入等待。

*.cs
// 余额,初始值为 1000
int balance = 1000;
// 用来请求锁的令牌
object locker = new();

// 函数 Pay,进行支付操作 void Pay(int amount) { // 请求排他锁,确保只有一个目标正在访问 balance lock (locker) { balance -= amount; // 修改余额后等待 5 秒 Thread.Sleep(5000); } }
// 获取当前的余额 int GetBalance() { // 请求排他锁,确保只有一个目标正在访问 balance lock (locker) return balance; }
Console.WriteLine("开启一个线程,支付 100"); Thread thread = new(() => Pay(100)); thread.Start();
Console.WriteLine("现在我要查看余额!"); // 调用 GetBalance 将陷入等待,因为此时 Pay 方法持有锁 Console.WriteLine($"余额:{GetBalance()}");
开启一个线程,支付 100
现在我要查看余额!
约 5 秒后…
余额:900

死锁

所谓的死锁,是指多个目标都在等待对方先释放持有的锁,导致他们均无法继续执行。在代码逻辑可以任意书写的情况下,死锁总是有可能发生,除非设置一些特殊的规则,比如,目标同一时间只能持有一个锁,在请求新的锁之前,必须释放旧的锁。当然,如果真的按照这种规则来书写代码,那么某些业务需求将无法被满足。

在下面的 C# 代码中,两个线程在持有自己的排他锁后,开始请求对方持有的锁,这导致了死锁的发生。

*.cs
// 用来请求锁的令牌
object lockerA = new();
object lockerB = new();

// 线程 I 先后使用令牌 lockerA,lockerB 获取排他锁 new Thread(() => { lock (lockerA) { Console.WriteLine("线程 I 已经获取锁 A"); Thread.Sleep(2000);
Console.WriteLine("线程 I 尝试获取锁 B"); lock (lockerB) Console.WriteLine("线程 I 已经获取锁 B"); } }).Start();
// 线程 II 先后使用令牌 lockerB,lockerA 获取排他锁 new Thread(() => { lock (lockerB) { Console.WriteLine("线程 II 已经获取锁 B"); Thread.Sleep(2000);
Console.WriteLine("线程 II 尝试获取锁 A"); lock (lockerA) Console.WriteLine("线程 II 已经获取锁 A"); } }).Start();
线程 I 已经获取锁 A
线程 II 已经获取锁 B
约 2 秒后…
线程 I 尝试获取锁 B
线程 II 尝试获取锁 A

源码

locks.cs·codebeatme/programming·GitHub