Как измерить интервал между двумя вызовами метода, не используя блокировку? - PullRequest
0 голосов
/ 12 сентября 2018

У меня есть функция, которая должна возвращать точное количество времени, прошедшее с момента последнего вызова.В настоящее время он реализован следующим образом:

    public TimeSpan Span()
    {
        lock (this)
        {
            var now = DateTime.UtcNow;
            var r = now - lastCallTime;
            lastCallTime = now;
            return r;
        }
    }

Моя проблема с этим методом заключается в том, что он использует блокировку, которая может оказать значительное влияние на производительность.

Это способ реализации этого без использования блокировок в целом

Ответы [ 2 ]

0 голосов
/ 12 сентября 2018

Просто для полноты я хочу показать вам, как более простой код (второе решение) из принятого ответа может вернуть отрицательное значение, как отмечено в этом ответе.

В этом мысленном эксперименте используются две нити, обозначенные 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();

Это то же решение, что и принятый выше ответ, только организовано немного по-другому.

0 голосов
/ 12 сентября 2018

Я бы порекомендовал использовать:

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 могут не «завершиться» в том же порядке, в котором они «начались».

Более простой подход для рассмотрения (но см. Предостережение ниже) будетbe:

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 (см. От Матфея)Отличный ответ Уотсона), возвращаемое TimeSpan может быть отрицательным в сценариях с высокой конкуренцией. Это не произойдет с первым решением, но первое решение будет медленнее с высокой конкуренцией.

...