系统正常运行时间&内存屏障

时间:2012-06-19 13:36:06

标签: c# multithreading memory-barriers

我需要一种强大的方法来获得系统正常运行时间,并最终使用如下内容。 添加了一些评论以帮助人们阅读它。我不能使用Task,因为它必须在.NET 3.5应用程序上运行。

// This is a structure, can't be marked as volatile
// need to implement MemoryBarrier manually as appropriate
private static TimeSpan _uptime;

private static TimeSpan GetUptime()
{
    // Try and set the Uptime using per counters
    var uptimeThread = new Thread(GetPerformanceCounterUptime);
    uptimeThread.Start();

    // If our thread hasn't finished in 5 seconds, perf counters are broken
    if (!uptimeThread.Join(5 * 1000))
    {
        // Kill the thread and use Environment.TickCount
        uptimeThread.Abort();
        _uptime = TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue);
    }

    Thread.MemoryBarrier();
    return _uptime;
}

// This sets the System uptime using the perf counters
// this gives the best result but on a system with corrupt perf counters
// it can freeze
private static void GetPerformanceCounterUptime()
{
    using (var uptime = new PerformanceCounter("System", "System Up Time"))
    {
        uptime.NextValue();
        _uptime = TimeSpan.FromSeconds(uptime.NextValue());
    }
}

我正在努力的部分应该放在Thread.MemoryBarrier()的哪个位置? 我在读取值之前放置它,但是当前线程或其他线程都可以写入它。以上看起来是否正确?

编辑,根据Daniel

回答

这是我实施的内容,非常感谢您的见解。

private static TimeSpan _uptime;

private static TimeSpan GetUptime()
{
    var uptimeThread = new Thread(GetPerformanceCounterUptime);
    uptimeThread.Start();

    if (uptimeThread.Join(5*1000))
    {
        return _uptime;
    }
    else
    {
        uptimeThread.Abort();
        return TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue);
    }
}

private static void GetPerformanceCounterUptime()
{
    using (var uptime = new PerformanceCounter("System", "System Up Time"))
    {
        uptime.NextValue();
        _uptime = TimeSpan.FromSeconds(uptime.NextValue());
    }
}

修改2

根据Bob的评论更新。

private static DateTimeOffset _uptime;

private static DateTimeOffset GetUptime()
{
    var uptimeThread = new Thread(GetPerformanceCounterUptime);
    uptimeThread.Start();

    if (uptimeThread.Join(5*1000))
    {
        return _uptime;
    }
    else
    {
        uptimeThread.Abort();
        return DateTimeOffset.Now.Subtract(TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue));
    }
}

private static void GetPerformanceCounterUptime()
{
    if (_uptime != default(DateTimeOffset))
    {
        return;
    }

    using (var uptime = new PerformanceCounter("System", "System Up Time"))
    {
        uptime.NextValue();
        _uptime = DateTimeOffset.Now.Subtract(
            TimeSpan.FromSeconds(uptime.NextValue()));
    }
}

2 个答案:

答案 0 :(得分:2)

Thread.Join已经确保uptimeThread执行的写操作在主线程上可见。您不需要任何明确的内存屏障。 (没有Join执行的同步,你需要在两个线程上都有障碍 - 在写入之后和读取之前)

但是,您的代码可能存在问题:写入TimeSpan结构不是原子的,主线程和uptimeThread可能同时写入它(Thread.Abort只是信号堕胎,但不等待线程完成中止),造成撕裂写入。 我的解决方案是在中止时根本不使用该字段。此外,对GetUptime()的多个并发调用可能会导致同样的问题,因此您应该使用实例字段。

private static TimeSpan GetUptime()
{
    // Try and set the Uptime using per counters
    var helper = new Helper();
    var uptimeThread = new Thread(helper.GetPerformanceCounterUptime);
    uptimeThread.Start();

    // If our thread hasn't finished in 5 seconds, perf counters are broken
    if (uptimeThread.Join(5 * 1000))
    {
        return helper._uptime;
    } else {
        // Kill the thread and use Environment.TickCount
        uptimeThread.Abort();
        return TimeSpan.FromMilliseconds(
            Environment.TickCount & Int32.MaxValue);
    }
}

class Helper
{
    internal TimeSpan _uptime;

    // This sets the System uptime using the perf counters
    // this gives the best result but on a system with corrupt perf counters
    // it can freeze
    internal void GetPerformanceCounterUptime()
    {
        using (var uptime = new PerformanceCounter("System", "System Up Time"))
        {
            uptime.NextValue();
            _uptime = TimeSpan.FromSeconds(uptime.NextValue());
        }
    }
}

但是,我不确定中止性能计数器线程是否会正常工作 - Thread.Abort()只会中止托管代码执行。如果代码在Windows API调用中挂起,则该线程将继续运行。

答案 1 :(得分:2)

AFAIK在.NET中的写入是易失性的,因此在每次读取之前,唯一需要内存栅栏的地方是,因为它们需要重新排序和/或缓存。引用a post by Joe Duffy

  

供参考,以下是我了解它们的规则   我尽可能简单地说:

Rule 1: Data dependence among loads and stores is never violated.
Rule 2: All stores have release semantics, i.e. no load or store may move after one.
Rule 3: All volatile loads are acquire, i.e. no load or store may move before one.
Rule 4: No loads and stores may ever cross a full-barrier. 
Rule 5: Loads and stores to the heap may never be introduced.
Rule 6: Loads and stores may only be deleted when coalescing adjacent loads and 
stores from/to the same location.
     

请注意,根据此定义,不需要非易失性负载   有任何与他们相关的障碍。所以负载可以自由   重新排序,写作可能会在他们之后移动(虽然不是之前,因为   规则2)。有了这个模型,这是您真正需要的唯一真实案例   第4条规定的全屏障的强度是防止   在商店之后是易变的负载的情况下重新排序。   没有屏障,说明可能会重新排序。