【发布时间】:2010-05-23 13:40:22
【问题描述】:
这是一个可以被持有的锁 一次只有一个执行线程 时间。尝试获取锁 通过另一个执行线程使 后一个循环直到锁定 发布。
当两个线程试图同时获取锁时如何处理?
我认为这个问题也适用于其他各种互斥锁实现。
【问题讨论】:
标签: language-agnostic concurrency mutex spinlock
这是一个可以被持有的锁 一次只有一个执行线程 时间。尝试获取锁 通过另一个执行线程使 后一个循环直到锁定 发布。
当两个线程试图同时获取锁时如何处理?
我认为这个问题也适用于其他各种互斥锁实现。
【问题讨论】:
标签: language-agnostic concurrency mutex spinlock
如上一张海报所示,每一种现代机器类型都有一种特殊的指令类别,称为“原子”,它们的运行方式与上一张海报所示的一样……它们至少针对指定的内存位置序列化执行。
在 x86 上,有一个 LOCK 汇编器前缀,指示机器应该以原子方式处理下一条指令。当遇到指令时,x86 上会发生几件事情。
对于 x86,有两个常用的指令用于实现锁。
所以,你可以像这样实现一个锁:
uint32 mylock = 0; 而 (cmpxchg(&mylock, 0, 1) != 0) ;我们自旋,等待锁,因此,自旋锁。
现在,解锁的指令不会表现出这些好的行为。根据您使用的机器,使用未锁定的指令,可以观察到各种违反一致性的行为。例如,即使在具有非常友好的内存一致性模型的 x86 上,也可以观察到以下情况:
线程 1 线程 2 移动 [w], 0 移动 [x], 0 mov [w], 1 mov [x], 2 mov eax, w mov eax, x mov [y], eax mov [z], eax在这个程序结束时,y 和 z 的值都可以是 0!。
无论如何,最后一点:x86 上的 LOCK 可以应用于 ADD、OR 和 AND,以便为指令获得一致的原子读-修改-写语义。例如,这对于设置标志变量并确保它们不会丢失很重要。没有它,你就会遇到这个问题:
线程 1 线程 2 与 [x]、0x1 与 [x]、0x2在这个程序结束时,x 的可能值是 1、2 和 0x1|0x2 (3)。为了获得正确的程序,您需要:
线程 1 线程 2 锁定与 [x]、0x1 锁定与 [x]、0x2希望这会有所帮助。
【讨论】:
取决于处理器和线程实现。大多数处理器都有可以原子执行的指令,在这些指令上您可以构建诸如自旋锁之类的东西。例如 IA-32 有一个执行原子交换的xchg 指令。然后,您可以实现一个简单的自旋锁,例如:
eax = 1;
while( xchg(eax, lock_address) != 0 );
// now I have the lock
... code ...
*lock_address = 0; // release the lock
【讨论】:
xchg 的实现是一个微码/硬件问题。最终,实际上没有任何事情发生在完全相同的时间,即使有多个处理器/内核它们共享一条总线和一个时钟。应该可以找到针对特定处理器的这些指令之一的控制路径(SPARC 可能是最简单的),但不太可能有一个普遍适用的特定答案。
xchg 之类的东西,并在诸如 xchg 之类的东西之上构建了自旋锁、互斥体等。