【发布时间】:2016-06-14 15:30:03
【问题描述】:
根据 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 方法,它返回原始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;
}
}
}
澄清一下,这段代码的想法是:
- 拥有一种简单快速的方法来检查自某个操作/事件以来是否已过时间,
- 如果从多个线程调用方法,则不应破坏状态,
- 必须不受操作系统时钟更改(用户更改、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和Interlocked.Read是我相信的锁定机制 -
@justin.m.chase:不,它们都是无锁的(同时确保原子性)。在 x64 平台上,它们甚至会被 JITted 到实际的 CPU 指令。
-
如果您要走那么远,为什么不自己调用 QueryPerformanceCounter 呢?
-
@ScottChamberlain:当然,我也可以这样做,我只是想检查一下我在这种方法中是否遗漏了什么?无论如何,这会更好,因为
Stopwatch实现不必在未来的 .NET 版本中保持固定。唯一的好处是它在内部将滴答声转换为毫秒,所以我不必检查TickFrequency。 -
@justin.m.chase:换句话说,当没有竞争时它快 2 倍,当两个线程竞争时快 100 倍。对于多线程,性能差异会更大。
标签: c# stopwatch atomic lockless