简单的无锁定秒表



根据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;
}
}
}

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

(更新)

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

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同步、时区等)不敏感

我会使用类似的方法:

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
}
}

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

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

我不知道你的异步代码是什么样子的,但在psuedo代码中,我会做任何一种:

Stopwatch watch = Stopwatch.Startnew();
DoAsyncWork((err, result) =>
{
Console.WriteLine("Time Elapsed:" + (watch.ElapsedMilliseconds / 1000.0));
// process results...
});

或者:

public DoAsyncWork(callback) // called asynchronously
{
Stopwatch watch = Stopwatch.Startnew();
// do work
var time = watch.ElapsedMilliseconds / 1000.0;
callback(null, new { time: time });
}

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

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

相关内容

  • 没有找到相关文章

最新更新