C#线程安全整数类SInt多线程计数异常问题排查
线程安全结构体SInt自增结果不符合预期问题分析
问题场景
定义了线程安全整数结构体SInt,通过1000个线程对实例s执行自增操作,预期结果为1000,但实际输出为989,且所有线程均已正常停止。
代码实现
SInt结构体代码
using System; using System.Threading; public struct SInt { private int _value; public SInt(int initialValue = 0) { _value = initialValue; } public int Value => Volatile.Read(ref _value); public int Add(int value) => Interlocked.Add(ref _value, value); public int Subtract(int value) => Interlocked.Add(ref _value, -value); public int Multiply(int value) { int initial, computed; do { initial = Value; computed = initial * value; } while (Interlocked.CompareExchange(ref _value, computed, initial) != initial); return computed; } public int Divide(int value) { if (value == 0) throw new DivideByZeroException(); int initial, computed; do { initial = Value; computed = initial / value; } while (Interlocked.CompareExchange(ref _value, computed, initial) != initial); return computed; } public int Increment() => Interlocked.Increment(ref _value); public int Decrement() => Interlocked.Decrement(ref _value); // 原运算符重载(存在问题) public static SInt operator +(SInt a, int b) { a.Add(b); return a; } public static SInt operator -(SInt a, int b) { a.Subtract(b); return a; } public static SInt operator *(SInt a, int b) { a.Multiply(b); return a; } public static SInt operator /(SInt a, int b) { a.Divide(b); return a; } public static SInt operator ++(SInt a) { a.Increment(); return a; } public static SInt operator --(SInt a) { a.Decrement(); return a; } public static bool operator ==(SInt a, SInt b) => a.Value == b.Value; public static bool operator !=(SInt a, SInt b) => a.Value != b.Value; public static bool operator <(SInt a, SInt b) => a.Value < b.Value; public static bool operator <=(SInt a, SInt b) => a.Value <= b.Value; public static bool operator >(SInt a, SInt b) => a.Value > b.Value; public static bool operator >=(SInt a, SInt b) => a.Value >= b.Value; public static implicit operator SInt(int value) => new SInt(value); public static implicit operator int(SInt sInt) => sInt.Value; public override bool Equals(object? obj) { if (obj is SInt other) { return this == other; } return false; } public override int GetHashCode() => Value.GetHashCode(); public override string ToString() => Value.ToString(); }
测试代码
using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; class Program { static void Main(string[] args) { List<Thread> threads = new List<Thread>(); SInt s = 0; Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); for (int i = 0; i < 1000; i++) { var t = new Thread(() => { s++; Thread.Sleep(1000); }); t.Priority = ThreadPriority.Highest; threads.Add(t); t.Start(); } foreach (var t in threads) t.Join(); stopwatch.Stop(); Console.WriteLine($"Time: {stopwatch.ElapsedMilliseconds / 1000.0} Seconds"); Console.WriteLine(s); Console.WriteLine(threads.Count(t => t.ThreadState == System.Threading.ThreadState.Stopped)); Console.ReadKey(); } }
问题根源
核心原因是SInt作为值类型(struct),运算符重载的实现方式错误:
以operator ++为例,原代码中参数a是按值传递的副本,执行a.Increment()修改的是副本的内部_value字段,随后返回的也是这个副本。当线程执行s++时,实际流程为:
- 复制全局变量
s到临时副本a - 对副本
a调用Increment()(修改的是副本的_value) - 将副本
a赋值回s
这整个过程不是原子操作!多线程环境下,多个线程可能同时读取到s的同一个值,各自修改副本后再赋值回s,导致后续线程的赋值覆盖了前面线程的修改,最终丢失部分自增操作。
修复方案
修改所有修改类运算符的重载,使用ref参数+ref返回,直接操作原变量的引用,避免副本操作:
// 修正后的运算符重载 public static ref SInt operator +(ref SInt a, int b) { a.Add(b); return ref a; } public static ref SInt operator -(ref SInt a, int b) { a.Subtract(b); return ref a; } public static ref SInt operator *(ref SInt a, int b) { a.Multiply(b); return ref a; } public static ref SInt operator /(ref SInt a, int b) { a.Divide(b); return ref a; } public static ref SInt operator ++(ref SInt a) { a.Increment(); return ref a; } public static ref SInt operator --(ref SInt a) { a.Decrement(); return ref a; }
修改后,s++会直接操作原变量s的引用,Increment()内部的Interlocked.Increment保证了自增操作的原子性,所有线程的自增操作都不会丢失,最终结果会符合预期的1000。
内容的提问来源于stack exchange,提问作者Minimus Heximus
相关产品推荐
相关产品推荐

