Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/277.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何在不使用锁的情况下测量方法两次调用之间的间隔?_C#_Performance_Time_Parallel Processing_Locking - Fatal编程技术网

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这是我将反复使用的图书馆的一部分。如果有一种方法可以在不锁定的情况下实现这一点,我愿意这样做