简单的无锁秒表
Simple lockless stopwatch
根据 MSDN,Stopwatch
class 实例方法对于多线程访问不安全。这也可以通过检查各个方法来确认。
但是,由于我在代码中的几个地方只需要简单的 "time elapsed" 计时器,我想知道它是否仍然可以无锁地完成,使用类似的东西:
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
的区别在于,此 class 基本上一直都是 运行,因此我不需要保留任何附加状态("running" 或 "stopped"),就像 Stopwatch
所做的那样。
(更新)
在@Scott 在下面的回答中提出建议后,我意识到 Stopwatch
提供了一个简单的静态 GetTimestamp
方法,returns 原始 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;
}
}
}
这段代码的思路,澄清一下,是:
- 有一种简单快速的方法来检查自某个 operation/event、
以来时间是否已经过去
- 如果从多个线程调用,方法不应破坏状态,
- 必须对 OS 时钟变化不敏感(用户变化、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
}
}
我建议创建 Stopwatch
的多个实例,并且只在同一个线程上读取它。
我不知道你的异步代码是什么样的,但在伪代码中我会这样做:
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
工作在不同的线程中完成工作,然后在完成时调用回调,编组回调用方线程。
第二个示例假定调用者正在处理线程,并且此函数自行完成所有计时,并将结果传递回调用者。
我建议的唯一更改是使用 Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedTicks);
而不是毫秒,因为如果您处于高性能模式,则可以从 QueryPerformanceCounter
.
获得亚毫秒的结果
根据 MSDN,Stopwatch
class 实例方法对于多线程访问不安全。这也可以通过检查各个方法来确认。
但是,由于我在代码中的几个地方只需要简单的 "time elapsed" 计时器,我想知道它是否仍然可以无锁地完成,使用类似的东西:
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
的区别在于,此 class 基本上一直都是 运行,因此我不需要保留任何附加状态("running" 或 "stopped"),就像 Stopwatch
所做的那样。
(更新)
在@Scott 在下面的回答中提出建议后,我意识到 Stopwatch
提供了一个简单的静态 GetTimestamp
方法,returns 原始 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;
}
}
}
这段代码的思路,澄清一下,是:
- 有一种简单快速的方法来检查自某个 operation/event、 以来时间是否已经过去
- 如果从多个线程调用,方法不应破坏状态,
- 必须对 OS 时钟变化不敏感(用户变化、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
}
}
我建议创建 Stopwatch
的多个实例,并且只在同一个线程上读取它。
我不知道你的异步代码是什么样的,但在伪代码中我会这样做:
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
工作在不同的线程中完成工作,然后在完成时调用回调,编组回调用方线程。
第二个示例假定调用者正在处理线程,并且此函数自行完成所有计时,并将结果传递回调用者。
我建议的唯一更改是使用 Interlocked.Exchange(ref _lastResetTime, _stopwatch.ElapsedTicks);
而不是毫秒,因为如果您处于高性能模式,则可以从 QueryPerformanceCounter
.