我有一个函数,它需要返回自上次调用以来所经过的精确时间。目前,它的实现方式如下:
public TimeSpan Span()
{
lock (this)
{
var now = DateTime.UtcNow;
var r = now - lastCallTime;
lastCallTime = now;
return r;
}
}这个方法的问题是它使用锁,这可能会对性能产生重大影响。
是一种在不使用锁的情况下实现这一点的方法吗?
发布于 2018-09-12 12:59:05
我建议使用:
public long lastTimestamp = Stopwatch.GetTimestamp();
public TimeSpan Span()
{
do
{
long oldValue = lastTimestamp;
long currentTimestamp = Stopwatch.GetTimestamp();
var previous = Interlocked.CompareExchange(ref lastTimestamp, currentTimestamp, oldValue);
if (previous == oldValue)
{
// We effectively 'got the lock'
var ticks = (currentTimestamp - oldValue) * 10_000_000 / Stopwatch.Frequency;
return new TimeSpan(ticks);
}
} while (true);
// Will never reach here
// return new TimeSpan(0);
}这将是线程安全的,而不需要显式的lock。如果在lastTimestamp上有争用,那么代码将循环,直到它工作。这确实意味着对Span的多次调用可能不会按照它们“启动”的顺序“完成”。
一种更简单的考虑方法(但请参阅下面的警告)是:
public long lastTimestamp = Stopwatch.GetTimestamp();
public TimeSpan Span()
{
long currentTimestamp = Stopwatch.GetTimestamp();
var previous = Interlocked.Exchange(ref lastTimestamp, currentTimestamp);
var ticks = (currentTimestamp - previous) * 10_000_000 / Stopwatch.Frequency;
return new TimeSpan(ticks);
}这将是线程安全的,而不需要显式的lock。Interlocked.Exchange 优胜 lock.
根据医生们,Interlocked.Exchange
将64位有符号整数设置为指定值,并返回原始值,作为原子操作。
这段代码更简单,但是由于Interlocked.Exchange的工作方式(参见Matthew的好答案),在高争用场景中返回的TimeSpan可能是负。这不会发生在第一个解决方案,但第一个解决方案将是较慢的高争用。
发布于 2018-09-12 14:29:23
为了完整起见,我想向您展示的是,从接受的答案中获得的更简单的代码(第二个解决方案)如何返回一个负值,正如在这个答案中所指出的。
这个思想实验使用了两个线程,分别是T1和T2.我用T1和T2作为堆栈变量的前缀,这样您就可以区分它们(如下所示)。
假设lastTimeStamp从900开始,当前时间是1000。
现在考虑以下交错线程操作:
T1: long currentTimestamp = Stopwatch.GetTimestamp();
=> T1:currentTimeStamp = 1000
T2: long currentTimestamp = Stopwatch.GetTimestamp();
=> T2:currentTimeStamp = 1010
T2: var previous = Interlocked.Exchange(ref lastTimestamp, T2:currentTimestamp);
=> T2:previous = 900, lastTimestamp = 1010
T1: var previous = Interlocked.Exchange(ref lastTimestamp, T1:currentTimestamp);
=> T1:previous = 1010, lastTimestamp = 1000
T1: var ticks = (T1:currentTimestamp - T1:previous)
=> ticks = 1000 - 1010 = -10
T2: var ticks = (T2:currentTimestamp - T2:previous)
=> ticks = 1010 - 900 = 110如您所见,线程T1将返回-10。
附录
下面是我的看法--我不想把秒表时间戳转换成TimeSpan;我只是把它放在从Stopwatch.GetTimestamp()返回的单元中,以保持简洁(而且会稍微快一些):
public static long Span()
{
long previous;
long current;
do
{
previous = lastTimestamp;
current = Stopwatch.GetTimestamp();
}
while (previous != Interlocked.CompareExchange(ref lastTimestamp, current, previous));
return current - previous;
}
static long lastTimestamp = Stopwatch.GetTimestamp();这是与上述公认的答案相同的解决方案,只是组织方式略有不同。
https://stackoverflow.com/questions/52295296
复制相似问题