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

C#多线程访问静态共享变量延迟问题及实现合理性咨询

多线程静态变量访问耗时过长问题

我在多线程间使用静态变量进行数据访问,但获取变量值耗时过长。场景说明如下:

我有一个静态类Results.cs,用于存储两个Process.cs实例的结果变量,代码如下:

public static int ResultsStation0 { get; set; }
public static int ResultsStation1 { get; set; }

两个Process实例的StationResult方法会同时被调用,ResultsStation0/ResultsStation1初始值为-1。由于两个结果不会同时生成,该方法会先设置当前线程对应的结果,然后通过死循环等待两个结果都就绪:

void StationResult(){

        Stopwatch sw = new Stopwatch();
        sw.Restart();

        switch (stationIndex) //Set the result of the station thread
        {
            case 0: Results.ResultsStation0 = 1; break;
            case 1: Results.ResultsStation1 = 1; break;
        }

        //Waits to get the results of both threads
        while (true)
        {
            if (Results.ResultsStation0 != -1 && Results.ResultsStation1 != -1)
            {
                break;
            }
        }

        Trace_Info("GOT RESULTS " + stationIndex + "Time: " + sw.ElapsedMilliseconds.ToString() + "ms");
        
        if (Results.ResultsStation0 == 1 && Results.ResultsStation1 == 1)
        {
            //set OK if both results are OK
            Device.profinet.WritePorts(new Enum[] { NOK, OK },
                                        new int[] { 0, 1 });
        }
 }

功能可以正常运行,但等待线程的Stopwatch耗时远超预期:偶尔为1ms,但多数时候高达80ms。我的疑问是:既然共享同一内存,为何耗时如此久?当前的线程间变量访问方式是否正确?


问题分析与解决方案

核心问题原因

  1. 内存可见性缺失
    默认的静态自动属性没有内存屏障,编译器或CPU会将变量值缓存到当前核心的本地缓存中,同时可能对读写指令重排。等待线程的死循环可能一直读取本地缓存里的旧值,直到缓存同步完成,这直接导致了延迟。

  2. 忙等待的资源浪费
    当前的死循环属于忙等待,会持续占用CPU资源。线程调度器可能会因为该线程占用过多资源而推迟其执行,同时也会影响其他线程的运行效率,间接拉长等待时间。

正确的线程间同步方式

方案1:用volatile保障内存可见性

给静态变量添加volatile关键字,禁止编译器和CPU对变量读写操作重排,强制每次读写都直接访问主内存:

// 注意:volatile不能搭配自动属性,需改为字段
public static volatile int ResultsStation0 = -1;
public static volatile int ResultsStation1 = -1;

该方案解决了缓存一致性问题,但仍存在忙等待的CPU占用问题,适合低延迟场景的临时优化。

方案2:用CountdownEvent实现高效同步

这是更优的方案,线程等待时会进入休眠状态,不占用CPU资源:

// 在Results类中定义同步对象
public static readonly CountdownEvent ResultsReady = new CountdownEvent(2);

// 修改StationResult方法
void StationResult(){
    Stopwatch sw = new Stopwatch();
    sw.Restart();

    // 设置当前线程的结果
    switch (stationIndex)
    {
        case 0: Results.ResultsStation0 = 1; break;
        case 1: Results.ResultsStation1 = 1; break;
    }

    // 通知同步对象:当前线程已完成结果写入
    Results.ResultsReady.Signal();

    // 等待所有线程完成
    Results.ResultsReady.Wait();

    Trace_Info("GOT RESULTS " + stationIndex + " Time: " + sw.ElapsedMilliseconds.ToString() + "ms");
    
    if (Results.ResultsStation0 == 1 && Results.ResultsStation1 == 1)
    {
        Device.profinet.WritePorts(new Enum[] { NOK, OK }, new int[] { 0, 1 });
    }
}

CountdownEvent会在收到指定次数的信号(这里是2次)后,自动释放所有等待的线程,既保证了内存可见性,又彻底避免了忙等待的资源浪费。

方案3:基于Task并行模型(.NET 4.0+)

如果代码可以适配任务并行模型,这种方式更简洁高效:

// 启动两个处理任务
var task0 = Task.Run(() => { Results.ResultsStation0 = 1; });
var task1 = Task.Run(() => { Results.ResultsStation1 = 1; });

// 等待两个任务全部完成
await Task.WhenAll(task0, task1);

// 后续处理逻辑
if (Results.ResultsStation0 == 1 && Results.ResultsStation1 == 1)
{
    Device.profinet.WritePorts(new Enum[] { NOK, OK }, new int[] { 0, 1 });
}

.NET任务调度器会自动管理线程,避免手动同步的复杂度,同时保证执行效率。

总结

当前的线程间变量访问方式不正确,核心问题是缺少内存可见性保障和低效的忙等待。推荐使用CountdownEvent或任务并行模型,既能保证数据即时可见,又能避免CPU资源浪费,大幅降低等待延迟。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.13 04:20:55