2016-01-26 6 views
-1

Скажем, у меня есть 3 функции, которые могут быть вызваны верхним слоем:Есть ли способ синхронизировать это без блокировок?

  • Start - будет вызываться только если мы не были начаты еще, или остановка была ранее называлась
  • Stop - будет только после успешного звонка в начало
  • Process - можно позвонить по номеру любой номер (одновременно на разные темы); если запущенно, будет вызывать в нижний слой

В Stop, он должен ждать, пока все Process звонков закончить вызов в нижний слой, и предотвратить любые дальнейшие вызовы. С запорным механизмом, я могу придумать следующий псевдокод:

Start() { 
    ResetEvent(&StopCompleteEvent); 
    IsStarted = true; 
    RefCount = 0; 
} 

Stop() { 
    AcquireLock(); 
    IsStarted = false; 
    WaitForCompletionEvent = (RefCount != 0); 
    ReleaseLock(); 
    if (WaitForCompletionEvent) 
    WaitForEvent(&StopCompleteEvent); 
    ASSERT(RefCount == 0); 
} 

Process() { 
    AcquireLock(); 
    AddedRef = IsStarted; 
    if (AddedRef) 
    RefCount++; 
    ReleaseLock(); 

    if (!AddedRef) return; 

    ProcessLowerLayer(); 

    AcquireLock(); 
    FireCompletionEvent = (--RefCount == 0); 
    ReleaseLock(); 
    if (FilreCompletionEvent) 
    SetEvent(&StopCompleteEvent); 
} 

Есть ли способ добиться того же поведения без запирающего механизма? Возможно, с некоторым причудливым использованием InterlockedCompareExchange и InterlockedIncremenet/InterlockedDecrement?

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

+0

Если присвоение 'IsStarted' является атомарным, вам даже нужны блокировки? «WaitForCompletionEvent» может быть другим. –

+0

Используйте C стандартную 'stdatomics'. – Olaf

+0

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

ответ

2

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

Обратите внимание, что это только псевдокод, для иллюстративных целей; он не видел компилятора. И хотя я считаю, что логика потоковой передачи звучит правильно, проверьте ее правильность или попросите эксперта проверить ее; программирование без блокировки - hard.

#define STOPPING 0x20000000; 
#define STOPPED 0x40000000; 
volatile LONG s = STOPPED; 
    // state and count 
    // bit 30 set -> stopped 
    // bit 29 set -> stopping 
    // bits 0 through 28 -> thread count 

Start() 
{ 
    KeClearEvent(&StopCompleteEvent); 
    LONG n = InterlockedExchange(&s, 0); // sets s to 0 
    if ((n & STOPPED) == 0) 
     bluescreen("Invalid call to Start()"); 
} 

Stop() 
{ 
    LONG n = InterlockedCompareExchange(&s, STOPPED, 0); 
    if (n == 0) 
    { 
     // No calls to Process() were running so we could jump directly to stopped. 
     // Mission accomplished! 
     return; 
    } 

    LONG n = InterlockedOr(&s, STOPPING); 
    if ((n & STOPPED) != 0) 
     bluescreen("Stop called when already stopped"); 
    if ((n & STOPPING) != 0) 
     bluescreen("Stop called when already stopping"); 

    n = InterlockedCompareExchange(&s, STOPPED, STOPPING); 
    if (n == STOPPING) 
    { 
     // The last call to Process() exited before we set the STOPPING flag. 
     // Mission accomplished! 
     return; 
    } 

    // Now that STOPPING mode is set, and we know at least one call to Process 
    // is running, all we need do is wait for the event to be signaled. 

    KeWaitForSingleObject(...); 

    // The event is only ever signaled after a thread has successfully 
    // changed the state to STOPPED. Mission accomplished! 

    return; 
} 

Process() 
{ 
    LONG n = InterlockedCompareExchange(&s, STOPPED, STOPPING); 
    if (n == STOPPING) 
    { 
     // We've just stopped; let the call to Stop() complete. 
     KeSetEvent(&StopCompleteEvent); 
     return; 
    } 
    if ((n & STOPPED) != 0 || (n & STOPPING) != 0) 
    { 
     // Checking here avoids changing the state unnecessarily when 
     // we already know we can't enter the lower layer. 

     // It also ensures that the transition from STOPPING to STOPPED can't 
     // be delayed even if there are lots of threads making new calls to Process(). 

     return; 
    } 

    n = InterlockedIncrement(&s); 
    if ((n & STOPPED) != 0) 
    { 
     // Turns out we've just stopped, so the call to Process() must be aborted. 

     // Explicitly set the state back to STOPPED, rather than decrementing it, 
     // in case Start() has been called. At least one thread will succeed. 
     InterlockedCompareExchange(&s, STOPPED, n); 
     return; 
    } 

    if ((n & STOPPING) == 0) 
    { 
     ProcessLowerLayer(); 
    } 

    n = InterlockedDecrement(&s); 
    if ((n & STOPPED) != 0 || n == (STOPPED - 1)) 
     bluescreen("Stopped during call to Process, shouldn't be possible!"); 

    if (n != STOPPING) 
     return; 

    // Stop() has been called, and it looks like we're the last 
    // running call to Process() in which case we need to change the 
    // status to STOPPED and signal the call to Stop() to exit. 

    // However, another thread might have beaten us to it, so we must 
    // check again. The event MUST only be set once per call to Stop(). 

    n = InterlockedCompareExchange(&s, STOPPED, STOPPING); 
    if (n == STOPPING) 
    { 
     // We've just stopped; let the call to Stop() complete. 
     KeSetEvent(&StopCompleteEvent); 
    } 
    return; 
} 
+0

Большое спасибо за это. На первый взгляд это выглядит правильно, но мне придется пройти намного подробнее. –

+0

Да, я полностью согласен. Если у вас есть какие-либо вопросы, например о том, почему я сделал что-то конкретным образом, я предлагаю вам отправить мне письмо, а не оставлять комментарии. Мой адрес электронной почты указан в моем профиле. –