【问题标题】:Is there a way to atomically flush a binary semaphore in C++ on Linux?有没有办法在 Linux 上以原子方式刷新 C++ 中的二进制信号量?
【发布时间】:2020-06-15 06:14:59
【问题描述】:

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

例如,VxWorks 有一个semFlush() API,它可以原子地解除所有挂在指定信号量上的任务,即,在允许运行之前,所有任务都会被解除阻塞。

我正在 Linux 上实现一个 C++ 类,它的行为类似于二进制信号量,并且还具有这种“刷新”功能。不幸的是,Linux 上的semaphore.h 不提供类似 API 的 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 StackOverflow

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() 时,多个线程可能会被解除阻塞。

有人有什么建议吗?

【问题讨论】:

  • semaphore.h 是过时的 sysv 接口。条件变量和 notify_all 与此处描述的“信号量”相匹配。 notify_all 应该足够了。另请注意,notify_all 应在仍持有关联互斥锁的情况下调用,除非您谨慎地管理它们的顺序非常。如果 notify_all 由于某种原因不起作用,您需要显示一个 minimal reproducible example 来演示该问题,而不是伪代码。

标签: c++ linux multithreading semaphore condition-variable


【解决方案1】:

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

“刷新”信号量然后推进一代。每个接受者在等待之前都会记录下它的世代,接受者会等待信号量为given以等待世代发生变化:

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

(警告:未经测试)

【讨论】:

  • flush() 函数应该执行given = true。只有give() 必须设置given = true,因为flush() 应该只影响等待它的线程,而不影响在调用flush() 之后调用take() 的任何线程。
  • @AyushSinha 您是否建议“当前”获取者然后调用 give 并使信号量再次有用?这似乎是合理的
  • 当前接受者或任何其他线程都可以调用 give()。我会把这个责任/选择权交给开发者。
  • 很公平。已编辑
猜你喜欢
  • 1970-01-01
  • 2023-03-16
  • 2015-09-20
  • 2017-06-22
  • 2020-01-09
  • 1970-01-01
  • 2012-12-22
  • 1970-01-01
  • 2011-04-30
相关资源
最近更新 更多