调用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
相关产品推荐
相关产品推荐

