虽然我熟悉并发编程概念,例如互斥和信号量,但我从未理解它们是如何在汇编语言级别实现的。
我想有一组内存“标志”说:
但是如何在线程之间同步访问这些标志?像这个天真的例子只会产生竞争条件:
mov edx, [myThreadId]
wait:
cmp [lock], 0
jne wait
mov [lock], edx
; I wanted an exclusive lock but the above
; three instructions are not an atomic operation :(
答案 0 :(得分:21)
xchg
换取CAS。因此从严格意义上讲,制作自旋锁不需要CAS - 但仍需要某种原子性。在这种情况下,它使用原子操作,该操作可以将寄存器写入存储器并以单步返回该存储器槽的先前内容。 (为了澄清一点: lock 前缀断言#LOCK信号,确保当前CPU具有对内存的独占访问权限。在今天的CPU上,它不一定以这种方式执行,而是效果通过使用xchg
,我们确保在读取和写入之间不会被抢占,因为指令不会在中途中断。所以如果我们有一个虚构的 lock mov reg0,mem / lock mov mem,reg1 对(我们没有),它们可能会在两个mov之间被抢占。)pause
指令作为您正在旋转的提示 - 这样您运行的核心可以在此期间做一些有用的事情答案 1 :(得分:10)
x86架构长期以来有一条名为xchg
的指令,它将寄存器的内容与内存位置进行交换。 xchg一直是原子的。
还有一个lock
前缀可以应用于任何一条指令,使该指令成为原子。在有多处理器系统之前,所有这一切确实是为了防止在锁定指令中间传递中断。 (xchg被隐式锁定)。
本文提供了一些使用xchg实现自旋锁的示例代码 http://en.wikipedia.org/wiki/Spinlock
当开始构建多CPU和更高版本的多核系统时,需要更复杂的系统来确保锁和xchg同步所有内存子系统,包括所有处理器上的l1缓存。大约在这个时候,对锁定和无锁算法的新研究表明原子CompareAndSet是一个更灵活的原语,所以更现代的CPU有这个作为指令。
附录:在评论 andras 中提供了一个“尘土飞扬”的指令列表,其中包含lock
前缀。 http://pdos.csail.mit.edu/6.828/2007/readings/i386/LOCK.htm
答案 2 :(得分:2)
我喜欢将线程同步视为自下而上的处理器和操作系统提供了更复杂的构造
在处理器级别,您有CAS和LL / SC,它允许您在单个原子操作中执行测试和存储...您还有其他处理器构造,允许您禁用和启用中断(但是它们被认为是危险......在某些情况下,你别无选择,只能使用它们)
操作系统提供了在每次线程使用其时间片时可能发生的任务之间的上下文切换的能力......或者由于更多的原因而发生的事情(我会发现)
然后有更高级别的构造,如互斥体,它使用处理器提供的这些原始机制(认为旋转互斥体)......它将持续等待条件变为真,并以原子方式检查该条件
然后这些旋转互斥体可以使用OS提供的功能(上下文切换和系统调用,如yield,将控件放弃到另一个线程)并为我们提供互斥量
这些构造进一步被更高级别的构造使用,如条件变量(可以跟踪多少线程正在等待互斥锁,以及当互斥锁变为可用时首先允许哪个线程)
这些结构可以进一步用于提供更复杂的同步结构......例如:信号量等