我知道自旋锁与自旋一起工作,存在不同的内核路径并且内核是抢占式的,那么为什么自旋锁在单处理器系统中不起作用?(例如,在 Linux 中)
5 回答
如果我理解您的问题,您会问为什么自旋锁在单核机器上是个坏主意。
它们应该仍然可以工作,但可能比真正的线程休眠并发昂贵得多:
当您使用自旋锁时,您实际上是在断言您认为您不必等待很长时间。你是说你认为用繁忙的循环来维护处理器时间片比让你的线程休眠和上下文转移到另一个线程或进程的成本更好。如果您必须等待很短的时间,您几乎可以立即入睡并被重新唤醒,但上下楼的成本比只是等待更昂贵。
这在多核处理器上更可行,因为它们具有比单核处理器更好的并发配置文件。在多核处理器上,在循环迭代之间,其他一些线程可能已经处理了您的先决条件。在单核处理器上,其他人不可能帮助你——你已经锁定了唯一的核心。
这里的问题是,如果你在锁上等待或休眠,你会暗示系统你还没有你需要的一切,所以它应该去做一些其他的事情,然后再回来找你。使用自旋锁,您永远不会告诉系统这一点,因此您将其锁定以等待其他事情发生 - 但与此同时,您正在阻止整个系统,因此其他事情不会发生。
自旋锁有不同的版本:
spin_lock_irqsave(&xxx_lock, flags);
... critical section here ..
spin_unlock_irqrestore(&xxx_lock, flags);
spin_lock_irqsave()
当数据需要在进程上下文和中断上下文之间共享时,应该使用Uni 处理器,因为在这种情况下 IRQ 也会被禁用。spin_lock_irqsave()
在任何情况下都可以工作,但部分是因为它们是安全的,它们也相当慢。但是,如果需要跨不同 CPU 保护数据,那么最好使用以下版本,因为在这种情况下不会禁用 IRQ,所以这些版本更便宜:
spin_lock(&lock);
...
spin_unlock(&lock);
在单处理器系统中,调用spin_lock_irqsave(&xxx_lock, flags);
与禁用中断具有相同的效果,这将提供所需的中断并发保护而无需不必要的 SMP 保护。然而,在多处理器系统中,这涵盖了中断和 SMP 并发问题。
自旋锁的本质是它不会取消进程的调度 - 相反,它会自旋直到进程获得锁。
在单处理器上,它要么立即获得锁,要么永远旋转——如果锁被争用,那么当前持有资源的进程将永远没有机会放弃它。自旋锁仅在另一个进程可以在一个正在旋转的锁上执行时才有用 - 这意味着多处理器系统。
自旋锁就其本质而言,旨在用于多处理器系统,尽管就并发性而言,运行抢占式内核的单处理器工作站的行为类似于 SMP。如果一个非抢占式单处理器系统曾经在锁上自旋,它将永远自旋;没有其他线程能够获得 CPU 来释放锁。出于这个原因,没有启用抢占的单处理器系统上的自旋锁操作被优化为什么都不做,除了那些改变 IRQ 屏蔽状态的操作。由于抢占,即使您从未期望您的代码在 SMP 系统上运行,您仍然需要实现适当的锁定。
参考:Linux 设备驱动程序 作者:Jonathan Corbet、Alessandro Rubini、Greg Kroah-Hartma
在操作系统三个简单的部分中找到以下两段可能会有所帮助:
对于自旋锁,在单 CPU 的情况下,性能开销可能会非常痛苦;想象一下持有锁的线程在临界区被抢占的情况。然后调度程序可能会运行所有其他线程(假设有 N - 1 个其他线程),每个线程都试图获取锁。在这种情况下,这些线程中的每一个都将在放弃 CPU 之前旋转一个时间片,这是对 CPU 周期的浪费。
但是,在多个 CPU 上,自旋锁工作得相当好(如果线程数大致等于 CPU 数)。思路如下:想象 CPU 1 上的线程 A 和 CPU 2 上的线程 B,两者都在争夺锁。如果线程 A (CPU 1) 抢到锁,然后线程 B 尝试,B 将自旋(在 CPU 2 上)。但是,大概关键部分很短,因此锁很快变得可用,并被线程 B 获取。在这种情况下,旋转等待另一个处理器上持有的锁不会浪费很多周期,因此可以有效