Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can std::condition_variables be used as counting semaphores?

This is a follow-up to Can C++11 condition_variables be used to synchronize processes?.

Can std::condition_variable objects be used as counting semaphores?

Methinks not because the object seems bound to a std::mutex, which implies it can only be used as a binary semaphore. I've looked online, including here, here, and here, but can't find reference or example of using these objects as counting semaphores.

like image 998
StoneThrow Avatar asked Oct 31 '16 02:10

StoneThrow


People also ask

What is std :: Condition_variable?

std::condition_variable The condition_variable class is a synchronization primitive that can be used to block a thread, or multiple threads at the same time, until another thread both modifies a shared variable (the condition), and notifies the condition_variable .

When would you use a counting semaphore?

Semaphores are typically used to coordinate access to resources, with the semaphore count initialized to the number of free resources. Threads then atomically increment the count when resources are added and atomically decrement the count when resources are removed.


1 Answers

Yes.

struct counting_sem {
  counting_sem(std::ptrdiff_t init=0):count(init) {}
  // remove in C++17:
  counting_sem(counting_sem&& src) {
    auto l = src.lock(); // maybe drop, as src is supposed to be dead
    count = src.count;
  }
  counting_sem& operator=(counting_sem&& src) = delete;
  void take( std::size_t N=1 ) {
    if (N==0) return;
    auto l = lock();
    cv.wait(l, [&]{
      if (count > 0 && count < (std::ptrdiff_t)N) {
        N -= count;
        count = 0;
      } else if (count >= (std::ptrdiff_t)N) {
        count -= N;
        N = 0;
      }
      return N == 0;
    });
  }
  void give( std::size_t N=1 ) {
    if (N==0) return;
    {
      auto l = lock();
      count += N;
    }
    cv.notify_all();
  }
  // reduce the count without waiting for it
  void reduce(std::size_t N=1) {
    if (N==0) return;
    auto l = lock();
    count -= N;
  }
private:
  std::mutex m;
  std::condition_variable cv;
  std::ptrdiff_t count;

  auto lock() {
    return std::unique_lock<std::mutex>(m);
  }
  auto unlocked() {
    return std::unique_lock<std::mutex>(m, std::defer_lock_t{});
  }
};

Code not tested or compiled, but the design is sound.

take(7) is not equivalent to for(repeat 7 times) take(): instead, it takes as much as it can then blocks if that wasn't enough.

Modifying so that it doesn't take anything until there is enough is easy:

      if (count >= (std::ptrdiff_t)N) {
        count -= N;
        N = 0;
      }
like image 58
Yakk - Adam Nevraumont Avatar answered Sep 28 '22 12:09

Yakk - Adam Nevraumont