【问题标题】:Is there a way to synchronize this without locks? [closed]有没有办法在没有锁的情况下同步它? [关闭]
【发布时间】:2016-01-26 15:24:31
【问题描述】:

假设我有 3 个函数可以被上层调用:

  • Start - 只有在我们还没有开始时才会被调用,或者 Stop 之前被调用过
  • Stop - 只有在成功调用 Start 后才会调用
  • 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 的奇特用法?

我问的原因是这是在网络驱动程序的数据路径中,我真的不希望有任何锁。

【问题讨论】:

  • 如果IsStarted 的赋值是原子的,你还需要锁吗? WaitForCompletionEvent 可能是另一回事。
  • 使用 C 标准 stdatomics.
  • 由于多个 Process 调用(进入 ProcessLowerLayer)可能会与 Stop 调用重叠,所以我必须等待任何仍在进行中的进程在 Stop 返回之前完成。
  • 基本上,它要么是某种形式的锁定和等待,要么是自旋锁——一个检查原子的繁忙循环。没有其他同步方式。选择你的毒药。
  • 在这种情况下,信号量可能是更好的方法。

标签: c++ c windows synchronization kernel-mode


【解决方案1】:

我相信可以避免使用显式锁和任何不必要的阻塞或内核调用。

请注意,这只是伪代码,用于说明目的;它还没有看到编译器。虽然我认为线程逻辑是合理的,但请自行验证其正确性,或请专家验证;无锁编程是困难

#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;
}

【讨论】:

  • 非常感谢。乍一看它看起来是正确的,但我将不得不更详细地介绍。
  • 是的,我完全同意。如果您有任何问题,例如,关于我为什么以特定方式做某事,我建议您给我发电子邮件而不是发表评论。我的电子邮件地址显示在我的个人资料中。
猜你喜欢
  • 1970-01-01
  • 2013-02-18
  • 1970-01-01
  • 2015-08-11
  • 2019-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-10
相关资源
最近更新 更多