对于特定应用,某些实现是否比其他实现更好?推出自己的产品有什么好处吗?
6 回答
查看 Wikipedia 上对Test-and-set机器指令的描述,它暗示了原子操作是如何在机器级别实现的。我可以想象大多数语言级别的互斥锁实现依赖于机器级别的支持,例如测试和设置。
基于 Adamski 的test-and-set
建议,您还应该了解“快速用户空间互斥锁”或futexes的概念。
互斥锁具有理想的特性,即在锁定或解锁非竞争互斥锁的常见情况下,它们不需要内核系统调用。在这些情况下,用户模式代码成功地使用原子比较和交换 (CAS)操作来锁定或解锁互斥体。
如果 CAS 失败,互斥体将被争用,并且内核系统调用(sys_futex
在 Linux 下)必须用于等待互斥体(在锁定情况下)或唤醒其他线程(在解锁情况下)。
如果您认真考虑自己实现此功能,请确保您还阅读了 Ulrich Drepper 的论文。
互斥体最好在操作系统内核中运行,同时尽可能缩短它周围的代码量,这样可以避免在任务切换到另一个进程时被中断。因此,确切的实现有点秘密。不过它并不复杂。它基本上是一个具有布尔字段的对象,它可以获取和设置该字段。
- 使用计数器时,它可以成为信号量。
- 互斥锁是临界区的起点,它在内部使用互斥锁来查看它是否可以进入一段代码。如果互斥锁是空闲的,它会设置互斥锁并执行代码,只有在完成后才释放互斥锁。当临界区注意到互斥锁被锁定时,它可以等待互斥锁被释放。
围绕基本互斥逻辑,有包装器将其包装在一个对象中。然后更多的包装器对象使其在内核之外可用。然后是另一个包装器,使其在 .NET 中可用。然后几个程序员将围绕这一切编写自己的包装代码,以满足他们自己的逻辑需求。包装器周围的包装器确实使它们成为一个模糊的领域。
现在,有了关于互斥体内部的这些基本知识,我只希望您将使用一种依赖于内核和底层硬件的实现。这些将是最可靠的。(如果硬件支持这些。)如果您使用的互斥锁在此内核/硬件级别上不起作用,那么它仍然是可靠的,但我建议不要使用它,除非别无选择。
据我所知,Windows、Linux 和 .NET 都将在内核/硬件级别使用互斥锁。
我链接到的 Wikipedia 页面解释了有关内部逻辑和可能实现的更多信息。优选地,互斥体由硬件控制,从而使互斥体的整个获取/设置成为不可分割的步骤。(只是为了确保系统不会在两者之间切换任务。)
Interlocked.CompareExchange
足以实现自旋锁。不过,要做好是相当困难的。请参阅Joe Duffy 的博客,了解其中涉及的微妙之处。
一些组装来演示原子锁定:
; BL is the mutex id
; shared_val, a memory address
CMP [shared_val],BL ; Perhaps it is locked to us anyway
JZ .OutLoop2
.Loop1:
CMP [shared_val],0xFF ; Free
JZ .OutLoop1 ; Yes
pause ; equal to rep nop.
JMP .Loop1 ; Else, retry
.OutLoop1:
; Lock is free, grab it
MOV AL,0xFF
LOCK CMPXCHG [shared_val],BL
JNZ .Loop1 ; Write failed
.OutLoop2: ; Lock Acquired
我使用 Reflector.NET 来反编译System.Threading.ReaderWriterLockSlim
.NET 框架的最新版本中添加的源代码。
它主要使用Interlocked.CompareExchange
,Thread.SpinWait
和Thread.Sleep
来实现同步。EventWaitHandle
在某些情况下使用了一些(内核对象)实例。
还增加了一些复杂性来支持单个线程上的重入。
如果您对此领域感兴趣并在 .NET 中工作(或者至少可以阅读它),那么您可能会发现查看该课程非常有趣。