War*_*ung 40
自旋锁是一种保护共享资源不被两个或多个进程同时修改的方法。第一个尝试修改资源的进程“获取”锁并继续前进,对资源做它需要做的事情。随后尝试获取锁的任何其他进程都会停止;据说它们“就地旋转”,等待第一个进程释放锁,因此称为自旋锁。
Linux 内核在许多事情上使用自旋锁,例如在向特定外围设备发送数据时。大多数硬件外围设备并非旨在处理多个同时状态更新。如果必须发生两种不同的修改,一个必须严格遵循另一个,它们不能重叠。自旋锁提供必要的保护,确保一次修改一次。
自旋锁是一个问题,因为自旋会阻止该线程的 CPU 内核执行任何其他工作。尽管 Linux 内核确实为在其下运行的用户空间程序提供了多任务处理服务,但该通用多任务处理功能并未扩展到内核代码。
这种情况正在改变,并且在 Linux 存在的大部分时间里都是如此。在 Linux 2.0 之前,内核几乎完全是一个单任务程序:每当 CPU 运行内核代码时,只使用一个 CPU 内核,因为有一个自旋锁保护所有共享资源,称为大内核锁(BKL) )。从 Linux 2.2 开始,BKL 逐渐被分解为许多独立的锁,每个锁保护更集中的资源类别。今天,在内核 2.6 中,BKL 仍然存在,但它只被真正旧的代码使用,这些代码不能很容易地移动到一些更细粒度的锁。现在,多核机器很可能让每个 CPU 都运行有用的内核代码。
由于 Linux 内核缺乏通用的多任务处理,因此分解 BKL 的实用性是有限的。如果 CPU 内核在内核自旋锁上被阻塞,则无法重新分配任务,直到释放锁才能执行其他操作。它只是坐着旋转,直到锁被释放。
自旋锁可以有效地将一个巨大的 16 核盒子变成一个单核盒子,如果工作负载是这样的,每个核心总是在等待一个自旋锁。这是 Linux 内核可扩展性的主要限制:将 CPU 内核从 2 个增加到 4 个可能会使 Linux 机器的速度几乎翻倍,但对于大多数工作负载而言,从 16 个增加到 32 个可能不会。
Sha*_*off 14
自旋锁是指进程不断轮询要删除的锁。它被认为是糟糕的,因为该过程正在不必要地消耗周期(通常)。它不是特定于 Linux 的,而是一种通用的编程模式。虽然它通常被认为是一种不好的做法,但实际上它是正确的解决方案;在某些情况下,使用调度程序的成本(就 CPU 周期而言)高于自旋锁预期持续的几个周期的成本。
自旋锁示例:
#!/bin/sh
#wait for some program to clear a lock before doing stuff
while [ -f /var/run/example.lock ]; do
sleep 1
done
#do stuff
Run Code Online (Sandbox Code Playgroud)
通常有一种方法可以避免自旋锁。对于这个特定示例,有一个名为inotifywait的 Linux 工具(默认情况下通常不安装)。如果它是用 C 编写的,您只需使用Linux 提供的inotify API。
同样的例子,使用 inotifywait 展示了如何在没有自旋锁的情况下完成同样的事情:
#/bin/sh
inotifywait -e delete_self /var/run/example.lock
#do stuff
Run Code Online (Sandbox Code Playgroud)
当一个线程尝试获取锁时,如果失败,可能会发生三件事 - 它可以尝试并阻塞,可以尝试并继续,可以尝试然后进入睡眠状态,告诉操作系统在发生某些事件时将其唤醒。
现在,尝试并继续使用的时间比尝试并阻止要少得多。让我们暂时假设“尝试并继续”将花费一个时间单位,而“尝试并阻止”将花费一百个时间。
现在让我们暂时假设一个线程平均需要 4 个单位的时间来持有锁。等待100个单位是浪费。因此,您可以编写一个“尝试并继续”的循环。在第四次尝试时,您通常会获得锁。这是一个自旋锁。之所以这样称呼是因为线程在获得锁之前一直在原地旋转。
附加的安全措施是限制循环运行的次数。因此,例如,您进行 for 循环运行,例如六次,如果失败,则您“尝试并阻止”。
如果您知道一个线程将始终持有锁,例如 200 个单位,那么您每次尝试和继续都在浪费计算机时间。
所以最终,自旋锁可能非常有效,也可能很浪费。当持有锁的“典型”时间高于“尝试并阻塞”所需的时间时,这是一种浪费。当持有锁的典型时间远低于“尝试和阻塞”的时间时,它是有效的。
Ps:关于线程的书是“A Thread Primer”,如果你还能找到的话。
甲锁定为两个或更多个任务(进程,线程)来同步的一种方式。具体来说,当两个任务都需要间歇性地访问一次只能由一个任务使用的资源时,这是安排任务不同时使用资源的一种方式。为了访问资源,任务必须执行以下步骤:
take the lock
use the resource
release the lock
Run Code Online (Sandbox Code Playgroud)
如果另一个任务已经取得锁,则不可能取得锁。(把锁想象成一个物理令牌对象。对象要么在抽屉里,要么有人手里拿着它。只有持有对象的人才能访问资源。)所以“拿锁”的真正意思是“等到没有其他人拥有锁,然后拿走它”。
从高层的角度来看,实现锁的主要方式有两种:自旋锁和条件。使用自旋锁,获取锁意味着只是“旋转”(即在循环中什么都不做),直到没有其他人拥有锁。有条件,如果一个任务试图获取锁但因为另一个任务持有它而被阻塞,则新来者进入等待队列;释放操作向任何等待的任务发出信号,表明锁现在可用。
(这些解释不足以让你实现一个锁,因为我没有说过原子性。但原子性在这里并不重要。)
自旋锁显然是浪费的:等待任务一直在检查是否获得了锁。那么为什么以及何时使用它呢?在没有持有锁的情况下,获得自旋锁通常非常便宜。当锁被持有的机会很小时,这使得它很有吸引力。此外,只有在预计不会花费很长时间才能获得锁的情况下,自旋锁才是可行的。所以自旋锁倾向于在它们将保持很短时间的情况下使用,以便大多数尝试在第一次尝试时会成功,而那些需要等待的不会等待很长时间。
Linux 设备驱动程序第 5 章中对 Linux 内核的自旋锁和其他并发机制有很好的解释。