简单的无锁秒表

Lou*_*Lou 5 c# atomic stopwatch lockless

根据 MSDN,类Stopwatch实例方法对于多线程访问并不安全。这也可以通过检查个别方法来确认。

然而,由于我只需要在代码中的几个地方使用简单的“经过时间”计时器,我想知道它是否仍然可以无锁地完成,使用类似的东西:

public class ElapsedTimer : IElapsedTimer
{
    /// Shared (static) stopwatch instance.
    static readonly Stopwatch _stopwatch = Stopwatch.StartNew();

    /// Stopwatch offset captured at last call to Reset
    long _lastResetTime;

    /// Each instance is immediately reset when created
    public ElapsedTimer()
    { 
        Reset();
    }

    /// Resets this instance.
    public void Reset()
    {
        Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedMilliseconds);
    }

    /// Seconds elapsed since last reset.
    public double SecondsElapsed
    {
        get
        {
             var resetTime = Interlocked.Read(ref _lastResetTime);
             return (_stopwatch.ElapsedMilliseconds - resetTime) / 1000.0;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

由于_stopwatch.ElapsedMilliseconds基本上是对 的调用QueryPerformanceCounter,我认为从多个线程调用是安全的?与常规的区别Stopwatch在于,这个类基本上一直在运行,所以我不需要像以前那样保留任何附加状态(“运行”或“停止”)Stopwatch

(更新)

在@Scott 在下面的答案中提出建议后,我意识到Stopwatch提供了一个简单的静态GetTimestamp方法,它返回原始QueryPerformanceCounter报价。也就是说,代码可以修改成这样,这样就是线程安全的:

public class ElapsedTimer : IElapsedTimer
{
    static double Frequency = (double)Stopwatch.Frequency;

    /// Stopwatch offset for last reset
    long _lastResetTime;

    public ElapsedTimer()
    { 
        Reset();
    }

    /// Resets this instance.
    public void Reset()
    {
        // must keep in mind that GetTimestamp ticks are NOT DateTime ticks
        // (i.e. they must be divided by Stopwatch.Frequency to get seconds,
        // and Stopwatch.Frequency is hw dependent)
        Interlocked.Exchange(ref _lastResetTime, Stopwatch.GetTimestamp());
    }

    /// Seconds elapsed since last reset
    public double SecondsElapsed
    {
        get
        { 
            var resetTime = Interlocked.Read(ref _lastResetTime);
            return (Stopwatch.GetTimestamp() - resetTime) / Frequency; 
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

澄清一下,这段代码的想法是:

  1. 有一种简单快速的方法来检查自某个操作/事件以来是否已经过去,
  2. 如果从多个线程调用,方法不应破坏状态,
  3. 必须对操作系统时钟更改不敏感(用户更改、NTP 同步、时区等)

我会像这样使用它:

private readonly ElapsedTimer _lastCommandReceiveTime = new ElapsedTimer();

// can be invoked by multiple threads (usually threadpool)
void Port_CommandReceived(Cmd command)
{
    _lastCommandReceiveTime.Reset();
}

// also can be run from multiple threads
void DoStuff()
{
    if (_lastCommandReceiveTime.SecondsElapsed > 10)
    {
        // must do something
    }
}
Run Code Online (Sandbox Code Playgroud)

Sco*_*ain 2

我建议的唯一更改是使用Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedTicks);而不是毫秒,因为如果您处于高性能模式,则可以从QueryPerformanceCounter.