在 Semaphore WaitOne 之后调用 Interlocked

calling Interlocked after Semaphore WaitOne

遇到以下代码,当同时调用 GenerateLabel 超过 4 次时,该代码会阻塞信号量。在 WaitOne 之后,成员 mCurrentScanner 用于访问扫描仪。问题是 WaitOne 之后是否需要联锁功能?我会说不,因为当释放 WaitHandle 时线程重新开始,但不是 100% 确定。

  mConcurrentLabels = new Semaphore(4, 4);

  public string GenerateLabel()
  {
    mConcurrentLabels.WaitOne();

    int current = 0;

    Interlocked.Exchange(ref current, mCurrentScanner);

    (scanner, dir) = ScanMappings[current];

    Interlocked.Increment(ref mCurrentScanner);
    mCurrentScanner %= 4;
    DoLongRunningTask();
    mConcurrentLabels.Release();
  }

就像你说的;信号量用于限制并发线程。但是正文仍然是并发执行的。所以需要locks/interlocked。

更大的问题是:使用 Interlocked.Exchange(ref current, mCurrentScanner); 安全地读取值并使用 Interlocked.Increment(ref mCurrentScanner);.

可能并发读取相同的值 Exchange() 并将其递增两次。因此,您将 select 一个值两次并跳过下一个值。

我还建议在使用信号量时使用 try/finallies。

mConcurrentLabels = new Semaphore(4, 4);

public string GenerateLabel()
{
    mConcurrentLabels.WaitOne();
    try
    {
        int current = Interlocked.Increment(ref mCurrentScanner);
        
        (scanner, dir) = ScanMappings[current];
        
        // mCurrentScanner %= 4;   <------ ?
        DoLongRunningTask();
    }
    finally
    {
        mConcurrentLabels.Release();
    }
}

但如果您需要 mod mCurrentScanner,我不会使用 Interlocked。

mConcurrentLabels = new Semaphore(4, 4);
object mSyncRoot = new object();

public string GenerateLabel()
{
    mConcurrentLabels.WaitOne();
    try
    {
        int current;
        
        lock(mSyncRoot)
        {
            current = mCurrentScanner++;
            mCurrentScanner %= 4;
        }
        
        (scanner, dir) = ScanMappings[current];
        
        // mCurrentScanner %= 4;   <------ ?
        DoLongRunningTask();
    }
    finally
    {
        mConcurrentLabels.Release();
    }
}

信号量的目的似乎是保护长运行ning 任务而不是保护对私有变量的访问。 从资源管理的角度来看,这很有用。例如,为了防止太多并发的长 运行ning 任务破坏数据库等共享资源。

需要互锁语句来保护私有变量,因为信号量允许此代码在不同线程上同时 运行 最多四次。

最好将此代码的主要部分放在 try {} finally{} 块中,以保证每次调用 mConcurrentLabels.WaitOne() 时恰好调用一次 mConcurrentLabels.Release()