again: mov ebx, 1
xchg [mutex], ebx ; try to claim mutex
test ebx, ebx ; did we succeed?
jz gotit
lea rax, [mutex] ; address to monitor
xor ecx, ecx ; no extensions
xor edx, edx ; no hints
monitor ; start to monitor the mutex
xor ecx, ecx ; no extensions
xor eax, eax ; no hints
mwait ; wait for mutex to change
jmp again ; once it changed, try to get the lock again
gotit: ...
1条答案
按热度按时间nwwlzxa71#
我们的想法是将
mwaitx
作为一种优化,而不是作为唯一的同步原语,对于这个用例,即使偶尔失败也没关系。例如,假设您要Assert自旋锁
如果它之前持有
0
,则将其设置为1
。简单的方法是忙着等待锁变为零,例如:这个循环当然效率很低:如果锁被另一个线程持有,它会旋转得非常快,导致大量代价高昂的RMW总线访问。我们可以使用
pause
指令来降低负载,但更好的是,一旦我们知道锁被持有,我们只会在知道另一个线程已经释放锁时尝试声明它。monitor
/mwait
指令提供了一种实现此操作的工具:你设置了一个被监控的地址,然后当有趣的事情发生时,你会被注意到。只有在那时,你才试图声明锁,如果你知道你不会得到它,你就不会被浪费。如果mwait
提前返回,也没有什么坏处:你就不能拿到锁然后又回去等它。虽然这一切都很好,但还有一个新问题:如果另一个线程暂时不让步,复杂的互斥体实现可能希望切换到另一个实现,例如,内核负责锁的实现。这允许等待锁变得可用,而线程不必实际运行,从而为其他用户释放资源。
使用
monitor
和mwait
很难实现这一点:等待时间是不确定的,可能会很长。AMD的mwaitx
和monitorx
指令非常相似,但修复了此问题:它们允许您设置一个超时,在此之后,即使内存区域没有更改,mwaitx
也会返回。这样,您可以使用类似“尝试通过旋转和等待来声明锁10次,然后升级到基于内核的锁”的算法,并合理地确定执行所需的时间帧。