线程同步在汇编语言级别是如何实现的?

15 浏览
0 Comments

线程同步在汇编语言级别是如何实现的?

虽然我对互斥锁和信号量等并发编程概念很熟悉,但我从未理解它们是如何在汇编语言层面实现的。

我想象中会有一组内存"标志",表示:

  • 锁A由线程1持有
  • 锁B由线程3持有
  • 锁C没有被任何线程持有
  • 等等

但是这些标志的访问在线程之间如何同步?像下面这个简单的例子只会产生竞态条件:

  mov edx, [myThreadId]
wait:
  cmp [lock], 0
  jne wait
  mov [lock], edx
  ; 我想要一个独占锁,但上述三条指令却不是原子操作 :(

0
0 Comments

在实践中,线程同步通常是通过CAS和LL/SC实现的。如果只需要自旋锁,可以使用x86/x64上的lock指令来实现,而不是CAS。在当前的架构中,使用CPU的原子操作和内存子系统提供的一致性协议是最常见的方法。此外,还需要考虑架构保证的缓存/内存一致性。对于自旋锁的实现,可以考虑使用TTAS自旋锁或指数退避等优化策略。在使用超线程的CPU上,可以使用pause指令来提供提示,让运行的核心在自旋期间执行其他有用的操作。另外,在一段时间后应该放弃自旋并将控制权让给其他线程。如果编写内核,可能还可以使用其他工具来实现线程调度和处理/启用/禁用中断。CAS和类似的操作被用于实现同步,因为CPU专门设计成将它们作为原子操作执行,即一次性完成,没有其他操作能够中断它们。

0
0 Comments

在处理器和操作系统级别上,线程同步的实现可以看作是自下而上的过程。在处理器级别上,有CAS和LL/SC等机制,可以在单个原子操作中进行测试和存储...还有其他处理器构造可以禁用和启用中断(但在特定情况下被认为是危险的...在某些情况下,你别无选择,只能使用它们)操作系统提供了在任务之间进行上下文切换的能力,这可以在一个线程使用完其时间片后发生...或者可能由其他原因引起(我将会提到)

然后,还有更高级的构造,比如互斥锁,它使用处理器提供的这些原始机制(比如自旋锁)...它会不断等待条件变为真,并以原子方式检查该条件

然后,这些自旋锁可以使用操作系统提供的功能(上下文切换和系统调用,如yield,将控制权交给另一个线程),从而得到互斥锁

这些构造体进一步被更高级的构造体如条件变量利用(它可以跟踪有多少个线程正在等待互斥锁,哪个线程先获得互斥锁)

然后,这些构造体可以进一步被用来提供更复杂的同步构造体...例如:信号量等

0
0 Comments

在x86架构中,有一条被称为xchg的指令,它可以交换一个寄存器与一个内存位置的内容。xchg一直以来都是原子操作的。同时,还存在一个lock前缀,可以应用于任意一条指令,使得该指令成为原子操作。在多处理器系统出现之前,lock前缀的作用只是防止在执行锁定指令过程中发生中断(xchg指令隐式地使用了lock前缀)。这篇文章提供了使用xchg实现自旋锁的示例代码。

当多CPU和后来的多核系统开始出现时,需要更复杂的机制来确保lock和xchg能够同步所有的内存子系统,包括所有处理器上的L1缓存。与此同时,对锁定和无锁算法进行的新研究表明,原子的CompareAndSet是一个更灵活的基本操作,因此现代的CPU都提供了这样一条指令。

附录:在评论中,andras提供了一份“陈旧的”指令列表,其中包括允许使用lock前缀的指令。

0