一些实现在特定应用方面比其他实现更好吗?自己开展有什么收益?
一些实现在特定应用方面比其他实现更好吗?自己开展有什么收益?
查看维基百科上Test-and-set机器指令的描述,这暗示了原子操作是如何在机器级别实现的。我可以想象大多数语言级别的互斥锁实现都依赖于诸如Test-and-set之类的机器级别支持。
在Adamski的test-and-set
建议的基础上,你还应该了解"快速用户空间互斥锁"或futexes的概念。
Futexes具有理想的属性,即在锁定或解锁未争用的互斥锁的常见情况下,它们不需要内核系统调用。在这些情况下,用户模式代码成功使用原子比较并交换(CAS)操作来锁定或解锁互斥锁。
如果CAS失败,则互斥锁被争用,并且必须使用内核系统调用--在Linux下是sys_futex
--以等待互斥锁(在锁定情况下)或唤醒其他线程(在解锁情况下)。
如果你认真考虑自己实现这个功能,请确保还阅读Ulrich Drepper的paper。
一个互斥锁最好在操作系统内核中运行,同时尽可能使其周围的代码量尽可能少,这样它就可以避免在切换到另一个进程时被截断。因此,确切的实现有点秘密。但它并不复杂。基本上,它是一个具有布尔字段的对象,它获取和设置该字段。
在基本互斥逻辑周围,有包装器将其包装成一个对象。然后有更多的包装器对象将其在内核外可用。然后又有另一个包装器将其在.NET中可用。然后几个程序员将编写自己的包装器代码以满足自己的逻辑需求。包装器周围的包装器使它们成为一个混沌的领域。
现在,有了关于互斥锁内部的基本知识,我希望您将使用依赖于内核和底层硬件的实现。这些将是最可靠的。(如果硬件支持这些。)如果您使用的互斥锁不在此内核/硬件级别上工作,则仍然可以可靠,但我建议不要使用它,除非没有其他选择。
据我所知,Windows、Linux和.NET都将在内核/硬件级别上使用互斥锁。
我已经链接的维基百科页面更详细地解释了内部逻辑和可能的实现。最好是由硬件控制互斥锁,从而使整个获取/设置互斥锁成为一个不可分割的步骤。(只是为了确保系统在其中不会切换任务。)以下是一些汇编代码,用于演示原子锁:
; 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
Interlocked.CompareExchange
足以实现自旋锁。但要正确地进行此操作相当困难。请参考Joe Duffy的博客,了解其涉及的微妙之处。
我使用Reflector.NET反编译了System.Threading.ReaderWriterLockSlim
的源代码,这个类是最近版本的.NET框架中添加的。
它主要使用Interlocked.CompareExchange
、Thread.SpinWait
和Thread.Sleep
来实现同步。在某些情况下,会使用一些EventWaitHandle
(内核对象)实例。
还有一些复杂性被添加以支持单线程上的可重入性。
如果你对这个领域感兴趣并且在.NET中工作(或者至少能够阅读它),那么你可能会发现检查这个类非常有趣。