C# 如何在不使用锁的情况下测量方法两次调用之间的间隔?
我有一个函数,它需要返回自上次调用以来经过的精确时间量。目前,它的实现方式如下:C# 如何在不使用锁的情况下测量方法两次调用之间的间隔?,c#,performance,time,parallel-processing,locking,C#,Performance,Time,Parallel Processing,Locking,我有一个函数,它需要返回自上次调用以来经过的精确时间量。目前,它的实现方式如下: public TimeSpan Span() { lock (this) { var now = DateTime.UtcNow; var r = now - lastCallTime; lastCallTime = now; return r; }
public TimeSpan Span()
{
lock (this)
{
var now = DateTime.UtcNow;
var r = now - lastCallTime;
lastCallTime = now;
return r;
}
}
我对这种方法的问题是,它使用锁,这可能会对性能产生重大影响
是一种不使用锁的实现方法吗?我建议使用:
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);
}
这将是线程安全的,不需要显式的锁
。如果在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);
}
这将是线程安全的,不需要显式的锁
<代码>联锁。交换通常锁定
根据,联锁。交换
:
将64位有符号整数设置为指定值并返回
原始值,作为原子操作
此代码更简单,但由于
互锁.Exchange
的工作方式(参见Matthew Watson的精彩回答),在高争用场景中返回的TimeSpan
可能是负的。第一个解决方案不会出现这种情况,但第一个解决方案会因高争用而变慢。我建议使用:
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);
}
这将是线程安全的,不需要显式的锁
。如果在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);
}
这将是线程安全的,不需要显式的锁
<代码>联锁。交换通常锁定
根据,联锁。交换
:
将64位有符号整数设置为指定值并返回
原始值,作为原子操作
此代码更简单,但由于
互锁.Exchange
的工作方式(参见Matthew Watson的精彩回答),在高争用场景中返回的TimeSpan
可能是负的。第一个解决方案不会出现这种情况,但第一个解决方案会因高争用性而变慢。为了完整起见,我想向您展示接受答案中更简单的代码(第二个解决方案)如何返回负值,如该答案中所述
这个思维实验使用两个线程,分别是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
[增编] 这是我的看法——我不想费心把秒表时间戳转换成时间跨度;为了简洁起见,我将它保留在从
Stopwatch.GetTimestamp()
返回的单位中(它会稍微快一点):
这是与上面接受的答案相同的解决方案,只是组织方式略有不同。为了完整起见,我想向您展示接受的答案中的更简单代码(第二个解决方案)如何返回负值,如该答案中所述 这个思维实验使用两个线程,分别是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
[增编] 这是我的看法——我不想费心把秒表时间戳转换成时间跨度;为了简洁起见,我将它保留在从
Stopwatch.GetTimestamp()
返回的单位中(它会稍微快一点):
这是与上述公认答案相同的解决方案,只是组织方式稍有不同。
DateTime
仅精确到50毫秒左右,因此这还不够好。您应该使用秒表,谢谢@mjwills我使用这个结合随机化,让软件以设定的时间频率执行不同的动作。这样实现它允许我不存储状态信息。间隔越不精确,软件执行的操作就越偏离我告诉它执行的操作锁定面临争用的频率有多高?1%的时间?70%?@mjwills这是我将反复使用的图书馆的一部分。如果有一种方法可以在不锁定的情况下实现这一点,我希望DateTime
只能精确到50毫秒左右,所以这还不够好。您应该使用秒表,谢谢@mjwills我使用这个结合随机化,让软件以设定的时间频率执行不同的动作。这样实现它允许我不存储状态信息。间隔越不精确,软件执行的操作就越偏离我告诉它执行的操作锁定面临争用的频率有多高?1%的时间?70%?@mjwills这是我将反复使用的图书馆的一部分。如果有一种方法可以在不锁定的情况下实现这一点,我愿意这样做