互斥锁如何在低级别等待解锁?
How do mutex lock waits for unlock at low level?
我想知道互斥量(或其他锁定实现)如何实现锁定功能的等待功能。我的意思是,是 cpu 指令将 mutex.lock 调用排队,是仅在 OS 中实现还是什么?
在我做的测试中,我认为这个等待功能只在 OS 层完成,并且创建了某种旋转,检查锁是否可以继续,如果没有把线程放到睡觉。是吗?
非常感谢。
它取决于平台。通常有一个自旋锁部分,如果达到固定的自旋限制,它会回退到操作系统中的阻塞。
自旋锁通常是通过在互斥量解锁时读取包含特定值的内存地址来实现的。如果它被视为未锁定,则会尝试将该值从未锁定值自动更改为锁定值。如果该原子交换成功,则互斥锁被锁定。通常会计算旋转次数,如果达到限制,我们会切换到 OS.
中的阻塞
OS 中的块通常以大致相同的方式实现,只是线程不是休眠,而是将自己添加到等待锁的列表中。当一个线程释放锁时,它会检查 OS 中是否有任何东西在等待,如果有,就解除阻塞。这会导致 OS 调度该线程。然后它通常会尝试执行自旋锁会尝试执行的相同原子交换,如果失败则在 OS 中再次阻塞。
在pseudo-code中:
锁定:
- 检查内存位置,看锁是否上锁。如果是,请转到步骤 3。
- 尝试自动将内存位置从解锁切换到锁定。如果我们成功,停止,我们持有锁。
- 增加旋转计数。如果我们没有旋转太多次,转到步骤 1。
- 自动增加等待此锁的线程数。
- 尝试自动将内存位置从解锁切换到锁定。如果我们成功,减少等待线程的数量并停止,我们持有锁。
- 有条件地阻止 OS。
- 转到第 5 步。
解锁:
- 以原子方式将保持锁定状态的内存位置设置为未锁定。
- 如果 OS 中等待这个锁的线程数大于零,告诉 OS 解除阻塞等待这个锁的所有线程。
请注意,OS 必须实施某种机制来避免在线程设法阻塞之前请求取消阻塞等待在 OS 中的任何线程的竞争。该方法从 OS 到 OS 不等。例如,Linux 有一个叫做 "futex" 的东西,它本质上是一种以原子方式实现锁定 pseudo-code 的步骤 4、5 和 6 的方法。
警告:如果您尝试在代码中实现此算法,请了解您可能会生产出性能几乎不如正确实现的玩具。您需要深入的 platform-specific 知识来避免可能掉入的令人讨厌的 performance-sucking 陷阱。例如,很容易编写一个自旋锁,使其在 CPU 秒内与 hyper-threading 共享物理核心的另一个线程窃取核心执行资源。并且很容易对成功的交换进行编码,以便 CPU 的分支预测预测它将失败,并且当您获得锁时,您会受到可怕的分支预测错误惩罚。
here 解释为:
What about waiting?
Now comes the tricky part. Well, only in a way is it tricky, in another way it is simple. The above test-and-set mechanism provides no support for a thread to wait on the value (aside from a CPU intensive spin-lock). The CPU doesn’t really understand high-level threads and processes, so it isn’t in a position to implement waiting. The OS must provide the waiting functionality.
In order for the CPU to wait correctly a caller is going to need to go through a system call. It is the only thing that can synchronise the various threads and provide the waiting functionality. So if we have to wait on a mutex, or release a waiting mutex, we have no choice but to call the OS. Most OSs have built in mutex primitives. In some cases they provide full fledged mutexes. So if a system call does provide a full mutex why would we bother with any sort of test-and-set in user space? The answer is that system calls have quite a bit of overhead and should be avoided when possible.
Various operating systems diverge greatly at this point, and will likely change as time goes on. Under linux there is a system call futex which provides mutex like semantics. It is specifically designed so that non-contention cases can be completely resolved in user space. Contention cases are then delegated to the operating system to handle in a safe, albeit far costlier manner. The waiting is then handled as part of the OS process scheduler.
我想知道互斥量(或其他锁定实现)如何实现锁定功能的等待功能。我的意思是,是 cpu 指令将 mutex.lock 调用排队,是仅在 OS 中实现还是什么?
在我做的测试中,我认为这个等待功能只在 OS 层完成,并且创建了某种旋转,检查锁是否可以继续,如果没有把线程放到睡觉。是吗?
非常感谢。
它取决于平台。通常有一个自旋锁部分,如果达到固定的自旋限制,它会回退到操作系统中的阻塞。
自旋锁通常是通过在互斥量解锁时读取包含特定值的内存地址来实现的。如果它被视为未锁定,则会尝试将该值从未锁定值自动更改为锁定值。如果该原子交换成功,则互斥锁被锁定。通常会计算旋转次数,如果达到限制,我们会切换到 OS.
中的阻塞OS 中的块通常以大致相同的方式实现,只是线程不是休眠,而是将自己添加到等待锁的列表中。当一个线程释放锁时,它会检查 OS 中是否有任何东西在等待,如果有,就解除阻塞。这会导致 OS 调度该线程。然后它通常会尝试执行自旋锁会尝试执行的相同原子交换,如果失败则在 OS 中再次阻塞。
在pseudo-code中:
锁定:
- 检查内存位置,看锁是否上锁。如果是,请转到步骤 3。
- 尝试自动将内存位置从解锁切换到锁定。如果我们成功,停止,我们持有锁。
- 增加旋转计数。如果我们没有旋转太多次,转到步骤 1。
- 自动增加等待此锁的线程数。
- 尝试自动将内存位置从解锁切换到锁定。如果我们成功,减少等待线程的数量并停止,我们持有锁。
- 有条件地阻止 OS。
- 转到第 5 步。
解锁:
- 以原子方式将保持锁定状态的内存位置设置为未锁定。
- 如果 OS 中等待这个锁的线程数大于零,告诉 OS 解除阻塞等待这个锁的所有线程。
请注意,OS 必须实施某种机制来避免在线程设法阻塞之前请求取消阻塞等待在 OS 中的任何线程的竞争。该方法从 OS 到 OS 不等。例如,Linux 有一个叫做 "futex" 的东西,它本质上是一种以原子方式实现锁定 pseudo-code 的步骤 4、5 和 6 的方法。
警告:如果您尝试在代码中实现此算法,请了解您可能会生产出性能几乎不如正确实现的玩具。您需要深入的 platform-specific 知识来避免可能掉入的令人讨厌的 performance-sucking 陷阱。例如,很容易编写一个自旋锁,使其在 CPU 秒内与 hyper-threading 共享物理核心的另一个线程窃取核心执行资源。并且很容易对成功的交换进行编码,以便 CPU 的分支预测预测它将失败,并且当您获得锁时,您会受到可怕的分支预测错误惩罚。
here 解释为:
What about waiting?
Now comes the tricky part. Well, only in a way is it tricky, in another way it is simple. The above test-and-set mechanism provides no support for a thread to wait on the value (aside from a CPU intensive spin-lock). The CPU doesn’t really understand high-level threads and processes, so it isn’t in a position to implement waiting. The OS must provide the waiting functionality.
In order for the CPU to wait correctly a caller is going to need to go through a system call. It is the only thing that can synchronise the various threads and provide the waiting functionality. So if we have to wait on a mutex, or release a waiting mutex, we have no choice but to call the OS. Most OSs have built in mutex primitives. In some cases they provide full fledged mutexes. So if a system call does provide a full mutex why would we bother with any sort of test-and-set in user space? The answer is that system calls have quite a bit of overhead and should be avoided when possible.
Various operating systems diverge greatly at this point, and will likely change as time goes on. Under linux there is a system call futex which provides mutex like semantics. It is specifically designed so that non-contention cases can be completely resolved in user space. Contention cases are then delegated to the operating system to handle in a safe, albeit far costlier manner. The waiting is then handled as part of the OS process scheduler.