vendredi 17 février 2023

c++: condition variable ownership

I am facing an issue while performing thread synchronisation.

I have a class very similar to the ThreadQueue implementation proposed in this answer, which I'll briefly report here for completeness:

#include <mutex>
#include <queue>
#include <condition_variable>

template <typename T>
class ThreadQueue {
  std::queue<T> q_;
  std::mutex mtx;
  std::condition_variable cv;

public:
  void enqueue (const T& t) {
    {
      std::lock_guard<std::mutex> lck(mtx);
      q_.push(t);
    }
    cv.notify_one();
  }

  T dequeue () {
    std::unique_lock<std::mutex> lck(mtx);
    cv.wait(lck, [this] { return !q_.empty(); });
    T t = q_.front();
    q_.pop();
    return t;
  }
};

I have a consumer that continuously extracts the first available item of a shared instance of that class, say ThreadQueue<int> my_queue;, until it receives a signal to quit, for instance:

std::atomic_bool quit(false);

void worker(){
  std::cout << "[worker] starting..." << std::endl;
  while(!quit.load()) {
      std::cout << "[worker] extract element from the queue" << std::endl;
      auto el = my_queue.dequeue();
       
      std::cout << "[worker] consume extracted element" << std::endl;
      std::cout << el << std::endl;
  }
    
  std::cout << "[worker] exiting" << std::endl;
}

Suppose the program has to terminate (for any reason) before any producer can insert elements in the queue; in this case the worker would be stuck on the line auto el = my_queue.dequeue(); and cannot terminate. An exemple of this case is the following:

int main() {
  std::thread t(worker);
  std::this_thread::sleep_for(std::chrono::seconds(1));
  
  std::cout << "[main] terminating..." << std::endl;
  quit.store(true);
  t.join();
  std::cout << "[main] terminated!" << std::endl;
  return 0;
}

Clearly, the worker can be "unlocked" by pushing a dummy element in the queue, but it does not seem an elegant solution.

I am thus wondering whether the thread syncronisation on the empty queue should be taken out of the ThreadQueue class and done inside the worker instead, i.e. moving the "ownership" of the condition variable outside the ThreadQueue container.

In general, is a class such as ThreadQueue always a bad design?

In case it's not, is there any solution that allows to keep the condition variable encapsulated in ThreadQueue, hence removing the responsibility of thread syncronisation from the users of that class (bearing in mind I am limited to usage of C++11)?

Full MWE here

Aucun commentaire:

Enregistrer un commentaire