整数的无锁多线程

Posted

技术标签:

【中文标题】整数的无锁多线程【英文标题】:Lockless multithreading of an integer 【发布时间】:2021-10-08 00:25:44 【问题描述】:

假设有一个函数在任何给定时间只能由一个线程执行,其余的只是返回(因为已经在处理特定的状态),实现这一点的最佳方法是什么?

public void RunOnce()

    if(Interlocked.Exchange(ref m_isRunning, 1) == 1)
        return;

    // Run code that should only be executed once
    
    // What mechanism do we use here to ensure thread safety?
    Volatile.Write(ref m_isRunning, 0);

如果 m_isRunning 是一个状态(即表示枚举的整数),同样的机制是否适用?

【问题讨论】:

您为什么要尝试使用无锁实现?您是否使用适当的同步原语测量了解决方案的性能问题? 编写无锁多线程代码很难测试(而且很脆弱;众所周知,维护程序员偶尔会从工作的无锁实现中删除必要的(但看似无用的)代码)。 C# 的lock 机制是轻量级的。你确定你需要自己动手吗? @Flydog57 竞争锁根本不是轻量级的。此外,如果我们一直告诉人们使用“锁”,因为替代方案很困难,那么没有人会学习如何编写高性能的多线程应用程序。 @Dennis19901 但是当锁被取出时你会返回,这将是真正的快。再一次,您是否真的测量过它的性能并确保它不适合您的使用?很有可能会没事的。编写一个在所有情况下都能正常运行的无锁版本的几率会大大降低。表现前的正确性。 Interlocked.Exchange(ref m_isRunning, 0);代替Volatile.Write 【参考方案1】:

恕我直言,您问题中的代码是线程安全的,但总的来说 Interlocked.CompareExchange 方法比Interlocked.Exchange 更灵活地实现无锁多线程。以下是我希望如何编写 RunOnce 方法:

int _lock; // 0: not acquired, 1: acquired

public void RunOnce()

    bool lockTaken = Interlocked.CompareExchange(ref _lock, 1, 0) == 0;
    if (!lockTaken) return;
    try
    
        // Run code that should be executed by one thread only.
    
    finally
    
        bool lockReleased = Interlocked.CompareExchange(ref _lock, 0, 1) == 1;
        if (!lockReleased)
            throw new InvalidOperationException("Could not release the lock.");
    

我的建议是使用Monitor 类:

object _locker = new();

public void RunOnce()

    bool lockTaken = Monitor.TryEnter(_locker);
    if (!lockTaken) return;
    try
    
        // Run code that should be executed by one thread only.
    
    finally  Monitor.Exit(_locker); 

...或者SemaphoreSlim 类,如果您更喜欢prevent reentrancy:

SemaphoreSlim _semaphore = new(1, 1);

public void RunOnce()

    bool lockTaken = _semaphore.Wait(0);
    if (!lockTaken) return;
    try
    
        // Run code that should be executed by one thread only.
    
    finally  _semaphore.Release(); 

恕我直言,它使您的代码更清晰。

【讨论】:

@Servy 我的意思是在任何给定时刻,只有一个线程被允许执行受保护部分中的代码。您认为当前的缩写评论是否具有误导性? 这不是误导,只是不准确。任意数量的线程可以同时执行该代码。当使用实际的同步机制时,评论是正确的,因为它们提供了这样的保证,但无锁代码提供了这种保证。这就是为什么这么多人谨慎使用它的原因,它对如何重新排序代码的限制要弱得多 @Servy 对不起,我不会写无数个程序来证明我的无锁实现保证了受保护区域的独占执行。我要做的是发布此链接:Does Interlocked.CompareExchange use a memory barrier?,它很好地涵盖了您对代码重新排序的考虑,恕我直言。最后,我要感谢您与我们分享您的知识。 @Dennis19901 在 finally 块中出现CompareExchange 的原因与SemaphoreSlim 在释放次数多于获得次数时抛出SemaphoreFullException 的原因相同。它用于检测软件错误。至于使用Interlocked.ExchangeVolatile.Write 而不是CompareExchange,现在我正在考虑它,在这种特定情况下实际上你可以。您问题中的代码是线程安全的恕我直言。 @TheodorZoulias 谢谢。您提出了一个很好的观点,即多次阻止“释放”。

以上是关于整数的无锁多线程的主要内容,如果未能解决你的问题,请参考以下文章

多线程编程之无锁队列

CAS 与原子操作

支持内部晋升的无锁并发优先级线程池

是否存在多个读取或写入线程的无锁队列?

带危险指针的无锁内存回收

Python网络编程(进程通信信号线程锁多线程)