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

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++时,实际流程为:

  1. 复制全局变量s到临时副本a
  2. 对副本a调用Increment()(修改的是副本的_value)
  3. 将副本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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 21:35:55