首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >如何在不使用锁的情况下测量方法的两个调用之间的间隔?

如何在不使用锁的情况下测量方法的两个调用之间的间隔?
EN

Stack Overflow用户
提问于 2018-09-12 12:33:24
回答 2查看 109关注 0票数 0

我有一个函数,它需要返回自上次调用以来所经过的精确时间。目前,它的实现方式如下:

代码语言:javascript
复制
    public TimeSpan Span()
    {
        lock (this)
        {
            var now = DateTime.UtcNow;
            var r = now - lastCallTime;
            lastCallTime = now;
            return r;
        }
    }

这个方法的问题是它使用锁,这可能会对性能产生重大影响。

是一种在不使用锁的情况下实现这一点的方法吗?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2018-09-12 12:59:05

我建议使用:

代码语言:javascript
复制
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的多次调用可能不会按照它们“启动”的顺序“完成”。

一种更简单的考虑方法(但请参阅下面的警告)是:

代码语言:javascript
复制
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);
}

这将是线程安全的,而不需要显式的lockInterlocked.Exchange 优胜 lock.

根据医生们Interlocked.Exchange

将64位有符号整数设置为指定值,并返回原始值,作为原子操作。

这段代码更简单,但是由于Interlocked.Exchange的工作方式(参见Matthew的好答案),在高争用场景中返回的TimeSpan可能是。这不会发生在第一个解决方案,但第一个解决方案将是较慢的高争用。

票数 2
EN

Stack Overflow用户

发布于 2018-09-12 14:29:23

为了完整起见,我想向您展示的是,从接受的答案中获得的更简单的代码(第二个解决方案)如何返回一个负值,正如在这个答案中所指出的。

这个思想实验使用了两个线程,分别是T1和T2.我用T1和T2作为堆栈变量的前缀,这样您就可以区分它们(如下所示)。

假设lastTimeStamp从900开始,当前时间是1000。

现在考虑以下交错线程操作:

代码语言:javascript
复制
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()返回的单元中,以保持简洁(而且会稍微快一些):

代码语言:javascript
复制
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();

这是与上述公认的答案相同的解决方案,只是组织方式略有不同。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/52295296

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档