You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

调用Semaphore的WaitOne之后是否还需要使用Interlocked相关操作?

问题结论

Interlocked 相关操作完全有必要保留,不能删除。

原因解释
  • 你当前对 Semaphore 的作用存在误解:你用的是初始计数、最大计数均为4的信号量,它的作用仅为限制最多4个线程同时进入WaitOne()到Release()之间的代码段,而非保证临界区串行执行。也就是说同一时间会有最多4个线程并行操作共享成员变量mCurrentScanner,不存在你以为的「线程执行上下文独立、不会有竞争」的情况。
  • 即便是读取mCurrentScanner的操作,多线程并行场景下也可能因为CPU缓存同步不及时、指令重排导致读到过期的值,Interlocked.Exchange保证了你读取到的mCurrentScanner是最新的内存值,避免出现不可预期的错误。如果删除Interlocked.Exchange直接赋值current = mCurrentScanner,在部分CPU架构下可能出现读取到旧值的问题。
  • 如果删除Interlocked.Increment操作,直接读写mCurrentScanner会出现典型的多线程竞争问题:比如两个线程同时读取到mCurrentScanner = 1,会拿到同一个扫描仪索引,之后同时执行自增操作,最终mCurrentScanner只会变成2而非预期的3,导致扫描仪重复分配、部分扫描仪未被调度的问题。
  • 额外注意:你当前的代码仍然存在线程安全隐患:Interlocked.Increment(ref mCurrentScanner)是原子操作,但后续的mCurrentScanner %= 4不是原子操作,可能出现某个线程自增后mCurrentScanner变为4,还没来得及执行取模,另一个线程就读取到了4作为索引,导致ScanMappings下标越界。建议将自增+取模的逻辑替换为基于Interlocked.CompareExchange的循环原子更新,保证整个修改过程的原子性。
优化后的原子更新参考代码
mConcurrentLabels = new Semaphore(4, 4);

public string GenerateLabel()
{
    mConcurrentLabels.WaitOne();
    try
    {
        int current = Interlocked.CompareExchange(ref mCurrentScanner, 0, 0);
        (scanner, dir) = ScanMappings[current];

        // 原子完成自增+取模操作
        int oldValue, newValue;
        do
        {
            oldValue = mCurrentScanner;
            newValue = (oldValue + 1) % 4;
        } while (Interlocked.CompareExchange(ref mCurrentScanner, newValue, oldValue) != oldValue);
        
        DoLongRunningTask();
        return /* 生成的标签值 */;
    }
    finally
    {
        // 建议把Release放到finally块,避免异常时信号量泄露
        mConcurrentLabels.Release();
    }
}

内容的提问来源于stack exchange,提问作者Serve Laurijssen

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 19:24:00