【问题标题】:calling Interlocked after Semaphore WaitOne在信号量 WaitOne 之后调用 Interlocked
【发布时间】:2021-09-07 08:58:23
【问题描述】:

遇到以下代码,当同时调用 GenerateLabel 超过 4 次时,这些代码会阻塞信号量。在 WaitOne 之后,成员 mCurrentScanner 用于访问扫描仪。问题是 WaitOne 之后是否需要 Interlocked 功能?我会说不,因为当 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();
  }

【问题讨论】:

  • 即使使用联锁方法,这似乎仍然存在问题,因为您所做的一切都是访问数组和修改/旋转 int,我建议您只使用锁并完成它跨度>
  • DoLongRunningTask 大约需要 10-20 秒,同时其他呼叫可能会进来,因此更多的是保护 DoLongRunningTask 所做的事情。但不确定是否绝对需要 Interlocked 来保护两个调用以获得相同的索引

标签: c# .net semaphore


【解决方案1】:

就像你说的;信号量用于限制并发线程。但是主体仍然是同时执行的。所以需要锁/互锁。

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

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

我还建议在使用信号量时使用 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();
    }
}

但如果你需要修改 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();
    }
}

【讨论】:

  • 谢谢,int current = (Interlocked.Increment(ref mCurrentScanner) - 1) % scans.Count();这是一个多线程服务,人们从安卓设备打印贴纸,现在它限制为同时打印 4 次,第 5 次必须等到 sema 发布
  • 如果mCurrentScanner 为零,Interlocked.Increment(ref mCurrentScanner) - 1 将在第一次调用时产生-1。同样执行% scans.Count(); 永远不会改变mCurrentScanner。它可能永远不会翻转,因为范围;-)
  • 没错,去掉-1确定
【解决方案2】:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多