Libtask源码解析之锁

本文转载自微信公众号「编程杂技」,码解作者theanarkh 。码解转载本文请联系编程杂技公众号。码解
libtask中其实不需要锁,码解因为libtask中协程是码解非抢占式的,不存在竞态条件。码解但是码解libtask还是实现了一套锁的机制。我们看一下这个锁机制的码解实现。首先我们看一下结构体。码解
struct QLock { // 锁持有者 Task *owner; // 等待该锁的码解队列 Tasklist waiting; };接着我们看一下锁的操作。
加锁
static int _qlock(QLock *l,码解 int block) { // 锁没有持有者,则置当前协程为持有者,码解直接返回,码解1表示加锁成功 if(l->owner == nil){ l->owner = taskrunning; return 1; } // 非阻塞,码解则直接返回,码解0表示加锁失败 if(!block) return 0; // 插入等待锁队列 addtask(&l->waiting, taskrunning); taskstate("qlock"); // 切换到其他协程 taskswitch(); // 切换回来时,如果持有锁的协程不是当前协程,则异常退出,因为只有持有锁才会被切换回来,见unqlock if(l->owner != taskrunning){ fprint(2, "qlock: owner=%p self=%p oopsn", l->owner, taskrunning); abort(); } return 1; }如果当前锁没有持有者,则当前协程X就变成锁的持有者,否则把协程X插入等待锁队列中,然后让出cpu,香港云服务器切换到其他协程。当后续锁被释放并被协程X持有时,协程X就会被唤醒继续持续。加锁可以分为阻塞和非阻塞两种模式。非阻塞就是加锁失败也不会切换协程。
// 阻塞式加锁 void qlock(QLock *l) { _qlock(l, 1); } // 非阻塞式加锁 int canqlock(QLock *l) { return _qlock(l, 0); }释放锁
接下来我们看一下释放锁的逻辑
// 释放锁 void qunlock(QLock *l) { Task *ready; // 锁并没有持有者,异常退出 if(l->owner == 0){ fprint(2, "qunlock: owner=0n"); abort(); } // 如果还有协程在等待该锁,则置为持有者,并且从等待队列中删除,然后修改状态为就绪并加入就绪队列 if((l->owner = ready = l->waiting.head) != nil){ deltask(&l->waiting, ready); taskready(ready); } }当锁被释放时,如果还有协程在等待该锁,则从等待队列中摘取一个节点,然后变成锁的持有者并从等待队列中删除。最后插入就绪队列等待调度。以上是一种互斥锁的实现。下面我们再来看一下读写锁机制,读写锁也是互斥的,但是在某些情况下也可以共享。我们看一下读写锁的数据结构。
struct RWLock { // 正在读的读者个数 int readers; // 当前正在写的写者,只有一个 Task *writer; // 等待读和写的队列 Tasklist rwaiting; Tasklist wwaiting; };接着我看一下加锁逻辑。服务器托管
加读锁
// 加读锁 static int _rlock(RWLock *l, int block) { /* 没有正在写并且没有等待写,则加锁成功,并且读者数加一 */ if(l->writer == nil && l->wwaiting.head == nil){ l->readers++; return 1; } // 非阻塞则直接返回 if(!block) return 0; // 插入等待读队列 addtask(&l->rwaiting, taskrunning); taskstate("rlock"); // 切换上下文 taskswitch(); // 切换回来了,说明加锁成功 return 1; }当且仅当没有正在写的写者和等待写的写者时,才能加读锁成功,否则根据加锁模式进行下一步处理,直接返回加锁失败或者插入等待队列,然后切换到其他协程。我们看到当有一个等待写的协程时(l->wwaiting.head != nil),则后续的读者就无法加锁成功,而是被插入等待队列,否则可能会引起写者饥饿。
加写锁
// 加写锁 static int _wlock(RWLock *l, int block) { // 没有正在写并且没有正在读,则加锁成功,并置写者为当前协程 if(l->writer == nil && l->readers == 0){ l->writer = taskrunning; return 1; } // 非阻塞则直接返回 if(!block) return 0; // 加入等待写队列 addtask(&l->wwaiting, taskrunning); taskstate("wlock"); // 切换 taskswitch(); // 切换回来说明拿到锁了 return 1; }当且仅当没有正在写的写者和没有正在读的读者时,才能加写锁成功。否则类似加读锁一样处理。
释放读锁
// 释放读锁 void runlock(RWLock *l) { Task *t; // 读者减一,如果等于0并且有等待写的协程,则队列第一个协程持有该锁 if(--l->readers == 0 && (t = l->wwaiting.head) != nil){ deltask(&l->wwaiting, t); l->writer = t; taskready(t); } }持有读锁,说明当前肯定没有正在写的写者,但是可能有等待写的写者和等待读的b2b信息网读者(因为有等待写的写者导致无法加锁成功)。当释放读锁时,如果还有其他读者,则其他读者可以继续持有锁,因为读者可以共享读锁,而写者保持原来状态。如果这时候没有读者但是有等待写的写者,则从队列中选择第一个节点成为锁的持有者,其他的写者则继续等待,因为写者不能共享写锁。
释放写锁
// 释放写锁 void wunlock(RWLock *l) { Task *t; // 没有正在写,异常退出 if(l->writer == nil){ fprint(2, "wunlock: not lockedn"); abort(); } // 置空,没有协程正在写 l->writer = nil; // 有正在读,异常退出,写的时候,是无法读的 if(l->readers != 0){ fprint(2, "wunlock: readersn"); abort(); } // 释放写锁时,优先让读者持有锁,因为读者可以共享持有锁,提高并发 // 读可以共享,把等待读的协程都加入就绪队列,并持有锁 while((t = l->rwaiting.head) != nil){ deltask(&l->rwaiting, t); l->readers++; taskready(t); } // 释放写锁时,如果又没有读者,并且有等待写的协程,则队列的第一个等待写的协程持有锁 if(l->readers == 0 && (t = l->wwaiting.head) != nil){ deltask(&l->wwaiting, t); l->writer = t; taskready(t); } }持有写锁,可能有等待写的写者和等待读的读者。这里是读者优先持有锁,因为读者可以共享持有锁,提高并发,如果没有读者,则再判断写者。
总结:单纯的互斥锁是比较简单的,读写锁就相对复杂一点,主要是要根据读锁和写锁的特性制定一些策略,比如避免饥饿问题。libtask的方式是,加写锁的时候,当无法持有锁的时候,申请者就会被插入等待等待队列。这个是没有什么好说的,加读者的时候,情况就复杂了点,如果这时候有读者正在持有锁,理论上,申请者也可以持有锁,因为读锁是共享的,但是单纯这样处理的话,可能会导致等待写的写者一直拿不到锁,所以这里需要判断是否有等待写的写者,如果有则当前申请者则不能再持有读锁,而是要加入等待队列。那么在释放锁的时候,当释放读锁时,优先让等待写的写者持有锁,再到等待读的读者持有锁。同样,当释放写锁时,优先让读者持有锁,这样就能比较好地平衡读者和写者持有锁的机会。
相关文章
深入了解Win10(掌握Win10的关键操作,提高工作效率)
摘要:随着科技的快速发展,Win10已经成为了目前最流行的操作系统之一。然而,许多人对Win10的使用仍然不够熟悉,无法充分发挥其功能和优势。本文将带您一起深入了解Win10的使用技巧和...2025-11-04- 摘要:电脑显卡作为计算机硬件中的重要组成部分,在实现图形处理和显示方面起着至关重要的作用。本文将重点解析电脑A卡显卡的性能和适用领域,帮助读者了解A卡显卡的特点和优势。1.电脑A...2025-11-04
使用mpallv3.72.0b恢复闪存教程(教你如何使用mpallv3.72.0b软件恢复闪存数据)
摘要:随着科技的不断进步,闪存设备在我们的生活中扮演着越来越重要的角色。然而,由于各种原因,我们经常会面临数据丢失的风险。为了帮助大家解决这个问题,本文将详细介绍如何使用mpallv3....2025-11-04老挑毛U盘装教程(轻松学会老挑毛U盘装,让你的U盘变得更实用更高效!)
摘要:随着科技的发展,U盘已经成为我们日常生活和工作中不可或缺的存储设备。然而,对于大部分人来说,U盘只是一个用来存储和传输文件的工具,并没有发挥出它更多的功能。本文将向大家介绍一种名为...2025-11-04- 摘要:在现代社会中,电脑已经成为我们生活和工作中不可或缺的一部分。而键盘作为电脑的重要输入设备,我们每天都要与之亲密接触。然而,很多人在使用笔记本电脑键盘时常常感到困惑,甚至影响到了打字...2025-11-04
- 摘要:随着科技的发展,手机系统更新迭代频繁,但是有些老旧的手机无法通过正常升级方式获得最新版本的系统。然而,通过使用电脑可以轻松将手机系统进行更新,本文将为您介绍以用电脑换手机系统安装的...2025-11-04

最新评论