问题

在linux下,假如一个线程由于持有暂时无法lock的mutex而休眠,这个线程何时会被重新调度?

回答
好的,我们来深入聊聊 Linux 下线程因锁(Mutex)阻塞而休眠,以及它何时会被重新调度的这个问题。我尽量不写得像个冷冰冰的机器说明,而是带点实际场景的思考。

想象一下,咱们的程序里有多个线程,它们像工厂里的工人,各自有自己的任务。但有些机器(资源)是共享的,一次只能有一个工人使用。这时候就需要一个“看门人”(Mutex)来管理,谁想用机器,就得先跟看门人打个招呼,拿到“通行证”(锁)。

现在,咱们的某个线程小明,想去用一台特别重要的机器,于是他去找看门人mutexA。结果发现,mutexA 已经被另一个线程小红拿去用了,而且小红还在忙,一时半会儿肯定放不开。

在这种情况下,小明就不能继续往前走了,他得停下来,等待。在操作系统看来,小明这个状态就是“阻塞”或者“休眠”了,他暂时不需要 CPU 来运转。

那么,小明什么时候才能“醒”过来,重新有机会获得 CPU 呢? 这不是一个简单的时间点,而是由一系列事件和调度策略共同决定的。

1. “看门人”被释放:最直接的唤醒信号

这是最直接也最根本的原因:持有 mutexA 的线程小红,最终会完成她的工作,并主动释放 mutexA。

当小红调用 `pthread_mutex_unlock()`(或者类似的函数)来释放 mutexA 时,这就像是看门人宣布:“这台机器现在空出来了!”

操作系统会收到这个信号,并且它知道 mutexA 有其他线程在等待。具体来说,当一个线程释放一个被其他线程持有的互斥锁时:

内核记录下来: 内核(Linux 的调度器)会知道这个互斥锁现在是可用的,并且有一个或多个线程在等待它。
将等待线程移出等待队列: 在内核内部,会有一些等待队列。当互斥锁被释放时,等待它的线程会被从“等待队列”移出来,放回“就绪队列”。

2. 调度器如何“看到”等待线程的就绪?

仅仅从“等待队列”移到“就绪队列”还不够,小明还需要 获得 CPU 的执行权。这就轮到 Linux 的调度器出场了。

Linux 的调度器是一个非常复杂的系统,它负责决定哪个线程(或进程)在什么时候运行。在抢占式调度(Preemptive Scheduling)的 Linux 环境下,调度器会周期性地(或者在某些事件发生时)检查哪些线程是“就绪”状态的,然后从中挑选一个最高优先级的线程来运行。

那么,当小明从等待状态变为就绪状态后,他是如何被调度器“看中”的呢?

时钟中断(Timer Interrupts): CPU 会有一个硬件时钟,它会定期产生中断。每次中断发生时,操作系统都会重新检查当前的调度情况。这是最基本、最持续的调度触发机制。如果小明在中断发生时正处于就绪状态,调度器就会考虑他。
系统调用或事件发生(System Call / Events): 除了时钟中断,还有很多其他事件会触发调度器的重新评估:
线程主动让出 CPU: 如果小明在等待锁的过程中,比如他调用了 `sched_yield()`,他会主动告诉调度器:“我现在有点累了,把 CPU 给别人用吧。”
其他线程运行完毕: 如果持有锁的线程小红完成了任务并退出了,她的退出也会触发调度。
I/O 完成: 如果小明在等待锁之前,还做了一些需要等待 I/O 的操作,当 I/O 完成时,他会从 I/O 等待状态变为就绪状态,这也会触发调度器的检查。
信号(Signals): 虽然不是直接原因,但信号也可以影响线程的状态和调度。
内核中的锁释放: 最关键的是,当 mutexA 被小红释放的那一刻,内核的锁管理子系统会负责将小明重新标记为“就绪”状态,并通知调度器。

3. 谁先拿到锁,谁先调度?优先级的影响

这里有一个关键点:持有锁的线程释放锁后,哪个等待线程会先拿到锁,以及哪个等待线程会先被调度? 这取决于几个因素:

锁的公平性(Fairness of Mutex):
公平锁(Fair Mutex): 如果 mutexA 是一个公平锁,那么等待时间最长的线程(也就是最先开始等待的那个)通常会最先被唤醒并获得锁。在这种情况下,小明如果等了最久,那么小红释放锁后,内核就会优先唤醒小明,让他获得锁。
非公平锁(Unfair Mutex): 很多默认的互斥锁是非公平的。这意味着,即使小明等了很久,但如果有一个新来的线程恰好在小红释放锁的那个瞬间也尝试获取锁,并且系统的某些优先级机制碰巧让这个新线程“看起来”更值得先被调度,那么新线程也可能抢先获得锁。
Linux Pthread Mutex 的默认行为: 默认情况下,Pthread 互斥锁(`pthread_mutex_t`)的实现不是严格公平的。这意味着等待时间长的线程不一定总是优先获得锁。调度器的优先级和时机在这个时候会扮演更重要的角色。

调度器的优先级和策略:
线程优先级(Thread Priority): 每个线程在 Linux 中都有一个优先级。通常,优先级高的线程会比优先级低的线程更早被调度到。如果小明是高优先级线程,而小红(持有锁)是低优先级线程,那么小红释放锁后,小明被调度起来的可能性就更高。
调度策略(Scheduling Policies): Linux 有多种调度策略,如 `SCHED_FIFO`(先进先出)、`SCHED_RR`(轮转)和 `SCHED_OTHER`(普通分时)。`SCHED_OTHER` 是最常见的,它使用复杂的算法(如 CFS Completely Fair Scheduler)来尽可能公平地分配 CPU 时间。`SCHED_FIFO` 和 `SCHED_RR` 是实时调度策略,优先级更高。
优先级继承(Priority Inheritance)和优先级天花板(Priority Ceiling): 为了解决优先级反转(Priority Inversion)的问题(高优先级线程被低优先级线程阻塞),有些锁机制会引入优先级继承。例如,如果小红是一个低优先级线程,但她持有的锁被一个高优先级线程小明所等待,那么在某些实现中,小红的优先级可能会被临时提升到与小明相同的级别,以便她能更快地执行并释放锁。但 Pthread 的普通互斥锁 不自动支持 优先级继承或优先级天花板。你可能需要使用 Pthread 的其他同步原语(如优先级继承的互斥锁 `PTHREAD_PRIO_INHERIT`)才能获得这些特性。

总结一下小明的“苏醒”之路:

1. 核心触发: 小明被阻塞,是因为 mutexA 被持有。他何时能“醒”来, 最根本的依赖于持有 mutexA 的线程(小红)何时释放它。
2. 内核的动作: 当小红释放 mutexA 后,内核的锁管理代码会检测到有线程(比如小明)在等待这个锁。
3. 移入就绪队列: 内核会将小明从锁的“等待队列”移到系统的“就绪队列”。
4. 调度器的介入:
周期性检查(时钟中断): 当时钟中断发生时,调度器会检查就绪队列中的线程。
事件触发: 其他系统事件也可能触发调度器的重新评估。
5. 获得 CPU: 调度器会根据线程的优先级、调度策略以及 CFS 等算法,从就绪队列中选择一个线程来运行。如果小明此时是就绪的,并且其优先级和调度状态允许,他就有可能被选中获得 CPU。
6. 再次尝试获取锁: 一旦小明获得了 CPU,他会再次尝试去获取 mutexA。因为mutexA已经被释放了(由小红),所以这次小明大概率能够成功获取锁,然后继续执行他的任务。

所以,小明被重新调度的时间点并不是固定的,它取决于:

锁的持有者何时释放锁。
锁本身的公平性策略。
小明在就绪队列中的优先级。
系统当前的整体调度负载和调度器的决策。

举个例子:

假设小红是个低优先级线程,她持有了锁。小明是个高优先级线程,他正在等待这个锁。
当小红执行完并释放锁时,小明被标记为就绪。由于小明是高优先级,Linux 调度器很可能会立即(或在下一个调度周期内)选择小明来运行。一旦小明获得 CPU,他就能立即尝试并获得 mutexA,继续他的工作。

又或者:

小红和小明都是普通优先级线程。小红持有锁。在小红释放锁时,系统可能正忙于运行其他几个普通优先级的线程。小明被加入了就绪队列。调度器需要等到当前正在运行的线程主动让出 CPU,或者时钟中断发生,并且在权衡了所有就绪线程后,才会轮到小明获得 CPU。

本质上,小明休眠是因为 等待一个不可用资源。他何时能被调度,是在 资源可用(锁被释放) 这个事件发生后,再 叠加了操作系统的调度决策。

网友意见

user avatar

mutex并不一定自带唤醒机制。

不过linux内核的mutex在无法得到锁的时候,会把自己加入一个waiter队列后进入等待状态。unlock的时候,解锁后会从这个waiter队列中取一个加入到调度器的就绪队列。但需要注意的是,即使这个任务加入到调度器后被立刻唤醒,也不意味着它就一定能得到这个锁,因为此时已经处于解锁状态,大家公平的抢而已。

所以,本质上mutex并不保证在A线程解锁后,立刻就有正在等待这个锁的线程被唤醒去抢这个锁——这个唤醒过程是任务调度器的问题。

而唤醒了之后能不能真的抢到锁,也是不确定的——哪怕是在单核的系统上也是如此(你不能保证你是第一个被唤醒的)。


最后,正常的生产者消费者模型,要避免无谓的重复竞争,难道不应该用semaphore(信号灯)吗?

类似的话题

本站所有内容均为互联网搜索引擎提供的公开搜索信息,本站不存储任何数据与内容,任何内容与数据均与本站无关,如有需要请联系相关搜索引擎包括但不限于百度google,bing,sogou

© 2025 tinynews.org All Rights Reserved. 百科问答小站 版权所有