首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >简易无锁秒表

简易无锁秒表
EN

Stack Overflow用户
提问于 2016-06-13 21:46:09
回答 2查看 566关注 0票数 5

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

但是,由于我只需要在代码中的几个位置使用简单的“时间流逝”计时器,所以我想知道是否仍然可以使用以下方法进行无锁操作:

代码语言:javascript
复制
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;
        }
    }
}

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

(更新)

在@Scott在下面的回答中提出建议之后,我意识到Stopwatch提供了一个简单的静态GetTimestamp方法,它返回原始的QueryPerformanceCounter滴答。换句话说,代码可以修改为线程安全的代码:

代码语言:javascript
复制
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; 
        }
    }
}

需要澄清的是,这一守则的思想是:

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

我会使用它类似于这样:

代码语言:javascript
复制
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
    }
}
EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2016-06-13 22:02:36

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

票数 2
EN

Stack Overflow用户

发布于 2016-06-13 21:59:09

我建议创建多个Stopwatch实例,并且只在同一个线程上读取。

我不知道您的异步代码是什么样子,但是在psuedo代码中,我会这样做:

代码语言:javascript
复制
Stopwatch watch = Stopwatch.Startnew();
DoAsyncWork((err, result) =>
{
  Console.WriteLine("Time Elapsed:" + (watch.ElapsedMilliseconds / 1000.0));
  // process results...
});

或者:

代码语言:javascript
复制
public DoAsyncWork(callback) // called asynchronously
{
  Stopwatch watch = Stopwatch.Startnew();
  // do work
  var time = watch.ElapsedMilliseconds / 1000.0;
  callback(null, new { time: time });
}

第一个示例假设DoAsyncWork工作是在一个不同的线程中完成的,然后在完成时调用回调,并将其编组回调用线程。

第二个例子假设调用者正在处理线程,并且这个函数自己执行所有的计时,将结果传回给调用者。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/37799650

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档