有没有办法在 Linux 上以原子方式刷新 C++ 中的二进制信号量?

Is there a way to atomically flush a binary semaphore in C++ on Linux?

一些内核在信号量上提供了一个“flush”操作来解锁所有等待信号量的任务。

例如,VxWorks 有一个 semFlush() API 可以自动解除阻塞所有挂起在指定信号量上的任务,即所有任务都将在允许 运行 之前解除阻塞。

我正在 Linux 上实现 C++ class,它的行为类似于二进制信号量,并且还具有此 "flush" 功能。不幸的是,Linux 上的 semaphore.h 没有像 APIs.

那样提供 flush() 或 broadcast()

我试过的:使用condition variables实现二进制信号量。 这是我的伪代码:

class BinarySem
{
    BinarySem();

    bool given;
    mutex m;
    condition_var cv;
    give();
    take();
    take( Timeout T );
    tryTake();
    flush();
}

BinarySem::BinarySem()
: given(false)
{}

// take(Timeout T), tryTake() not shown
// to make question concise on Whosebug

BinarySem::give()
{
    {
        lock_guard lk(m);
        given = true;
    }
    cv.notify_one();
}   

BinarySem::flush()
{
    {
        lock_guard lk(m);
        given = true;
    }
    cv.notify_all();
}

BinarySem::take()
{
    unique_lock lk(m);
    while(!given)
    {
        cv.wait(lk);
    }
    given = false;
    lk.unlock();
}

但是,此 flush() 不会以正确的方式运行。 比如说,我们有 2 个线程在等待 BinarySem(即它们都调用了 take())。 让这些线程为 hiPrioThreadloPrioThread.

当在 BinarySem 对象上调用 flush() 时,hiPrioThread 将从 take() 和 运行 退出。 当它屈服时(hiPrioThread 只是屈服,它还没有退出),loPrioThread 仍然无法 运行 因为布尔值 given 现在是 false再次。布尔值是防止虚假唤醒所必需的。

相反,信号量的 flush() 函数应该解除所有线程的阻塞,只要有机会它们就可以 运行。

如果我不在take()末尾设置given = false怎么办?这将使我的代码容易受到虚假唤醒的影响,然后在使用 give() 时多个线程可能会畅通无阻。

有人有什么建议吗?

从一些 "CyclicBarrier" 实现中借用一个概念,并有一个生成或循环计数器。

"Flushing" semaphore 则在推进生成。每个taker在等待之前都会记下自己的generation,taker等待信号量为given or generation发生变化:

BinarySem::flush() {
  {
    lock_guard lk(m);
    current_gen++;    // "flush" all waiters from the previous gen
    //given = true;   // No need to give; the 'current' taker will do this when done
  }
  cv.notify_all();
}

BinarySem::take() {
  lock_guard lk(m);
  uint64_t my_generation = current_gen;
  while (!given && my_generation == current_gen) {
    cv.wait(lk);
  }
  if (my_generation == current_gen) {
    given = false;
  }
}

(警告:未经测试)