Использование синхронизации Interlocked.CompareExchange с объектом синхронизации в обработчике Timer HandleElapsed

Я читаю пример MSDN http://msdn.microsoft.com/en-us/library/system.timers.timer.stop.aspx

В примере с timer.stop я подозревал, что его способ использования Interlocked.CompareExchange неверен.

private static void HandleElapsed(object sender, ElapsedEventArgs e)
{
    numEvents += 1;

    // This example assumes that overlapping events can be 
    // discarded. That is, if an Elapsed event is raised before  
    // the previous event is finished processing, the second 
    // event is ignored.  
    // 
    // CompareExchange is used to take control of syncPoint,  
    // and to determine whether the attempt was successful.  
    // CompareExchange attempts to put 1 into syncPoint, but 
    // only if the current value of syncPoint is zero  
    // (specified by the third parameter). If another thread 
    // has set syncPoint to 1, or if the control thread has 
    // set syncPoint to -1, the current event is skipped.  
    // (Normally it would not be necessary to use a local  
    // variable for the return value. A local variable is  
    // used here to determine the reason the event was  
    // skipped.) 
    // 
    int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
    if (sync == 0)
    {
        // No other event was executing. 
        // The event handler simulates an amount of work 
        // lasting between 50 and 200 milliseconds, so that 
        // some events will overlap. 
        int delay = timerIntervalBase 
            - timerIntervalDelta / 2 + rand.Next(timerIntervalDelta);
        Thread.Sleep(delay);
        numExecuted += 1;

        // Release control of syncPoint.
        syncPoint = 0;
    }
    else
    {
        if (sync == 1) { numSkipped += 1; } else { numLate += 1; }
    }
}

Мой вопрос в этом блоке

int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
if (sync == 0)
{
   // lots of code here
    syncPoint = 0;
}

Должно ли это быть как

int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
if (sync == 0)
{
// lots of code here
   Interlocked.CompareExchange(ref syncPoint, 0, 1)
}

Поскольку исходное назначение syncPoint = 0; не является потокобезопасным. Я прав?

ОБНОВЛЕНИЕ:

Я обновил пример, у меня нет вопросов о возвращаемом значении ComareExchange, мой вопрос касается назначения переменной syncpoint в конце этого блока if. Блокировки на нем нет вообще.


person ValidfroM    schedule 15.08.2013    source источник


Ответы (1)


Это атомарно, поэтому потокобезопасно

int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0);
if (sync == 0)

Это означает: если syncPoint равен 0, поместите его в 1, дайте мне старое значение (то есть 0) и поместите его в синхронизацию (локальную переменную). Затем проверьте, был ли выполнен обмен (так, если старое значение было 0)

Что я хотел бы изменить, так это вместо

syncPoint = 0;

я хотел бы использовать

Interlocked.Exchange(ref syncPoint, 0);

просто чтобы быть уверенным, что другие потоки сразу увидят, что "блокировка" свободна, иначе текущий поток может задержать "запись" этого syncPoint = 0 и никакие другие потоки не смогут войти в "блокировку".

НО

в приведенном примере это не проблема: HandleElapsed вызывается в ответ на событие (Таймер, вероятно, использует таймеры ОС)... Я даже не могу представить, что после обработки события нет никакого кода, который генерирует барьер памяти (внутри кода .NET или внутри кода ОС Windows), чтобы syncPoint обновление было видимым для других потоков. Единственная разница в том, что, возможно, он станет видимым «на несколько сотен строк ниже этой точки», а не «сразу сейчас».

Давайте проверим эту теорию: здесь

Если свойство SynchronizingObject имеет значение null, событие Elapsed возникает в потоке ThreadPool. Если обработка события Elapsed длится дольше, чем Interval, событие может быть вызвано снова в другом потоке ThreadPool. В этой ситуации обработчик событий должен быть реентерабельным.

Итак, событие запускается в новом потоке ThreadPool... Который после события обязательно возвращается в ThreadPool... Хорошо... Где-то наверняка есть MemoryBarrier (хотя бы для возврата потока в ThreadPool или в Sleep если один и тот же поток используется более одного раза)

person xanatos    schedule 15.08.2013
comment
Поправьте меня пожалуйста. Поскольку здесь есть барьер памяти, обновление будет обновляться по порядку. зачем нам еще нужен int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); если (sync == 0) на первом месте? - person ValidfroM; 15.08.2013
comment
Хорошо, тогда я сначала проведу небольшое исследование - person ValidfroM; 15.08.2013
comment
давайте продолжим это обсуждение в чате - person xanatos; 15.08.2013