2
\$\begingroup\$

I have two threads, one produces images and one processes them. For the synchronization, I created a class where you can set and get images, and it always waits until an image is available or a worker thread is not busy anymore.

Additionally, a call to SetFinish stops both threads, while a call to Clear clears the currently (not yet processed) image.

Do you see any problems with this code (mainly threading issues)?

Header:

#ifndef SHARED_QUEUE_H_
#define SHARED_QUEUE_H_

#include <memory>
#include <condition_variable>

struct ImageData;

class SharedQueue {
public:
  void SetFinish();
  bool GetImage(std::shared_ptr<const ImageData> &image);
  void SetImage(std::shared_ptr<const ImageData> image);
  void Clear();

private:
  std::condition_variable image_available_;
  std::condition_variable image_processed_;
  std::shared_ptr<const ImageData> next_image_;
  bool stop_{false};
  std::mutex mutex_;
};

#endif  // SHARED_QUEUE_H_

Implementation:

#include "shared_queue.h"

void SharedQueue::SetFinish() {
  { // Store flag and wake up the thread
    std::lock_guard<std::mutex> lock(mutex_);
    stop_ = true;
  }
  image_available_.notify_one();
  image_processed_.notify_one();
}

bool SharedQueue::GetImage(std::shared_ptr<const ImageData> &image) {
  {
    std::unique_lock<std::mutex> lock(mutex_);
    image_available_.wait(lock, [this]{
      return (next_image_.get() != nullptr || stop_);
    });

    if (stop_)
      return false;

    image = next_image_;
    next_image_.reset();
  }
  image_processed_.notify_one();
  return true;
}

void SharedQueue::SetImage(std::shared_ptr<const ImageData> image) {
  { // Store image for processing and wake up the thread
    std::unique_lock<std::mutex> lock(mutex_);
    image_processed_.wait(lock, [this]{
      return (next_image_.get() == nullptr || stop_);
    });

    if (stop_)
      return;

    next_image_ = image;
  }
  image_available_.notify_one();
}

void SharedQueue::Clear() {
  {
    std::unique_lock<std::mutex> lock(mutex_);
    next_image_.reset();
  }
  image_processed_.notify_one();
}
\$\endgroup\$

1 Answer 1

2
\$\begingroup\$

Not a Queue!

First and foremost, your SharedQueue isn't a queue. You can only store one element in it at a time. That doesn't make it super useful - what if the producer wants to write two images?

queue.setImage(img1);
queue.setImage(img2); // blocks?

It's more of a guarantee-one-at-a-time container. A queue would be much more useful, so I'd consider actually implementing one. This is a pretty major design flaw.


Beyond that, I just have minor comments.

Move semantics

You have a lot of copies where you can do moves. For instance, in SetImage():

next_image_ = image;

should be:

next_image_ = std::move(image);

Moving is cheaper than copying (no need to incur reference counting).

Checking shared_ptr

You don't need to use .get(), you can directly check the shared_ptr:

image_processed_.wait(lock, [this]{
   return !next_image_ || stop_;
});

Clear()

You use a std::unique_lock<> to Clear() where a std::lock_guard<> is sufficient. You use the correct one in SetFinish().

\$\endgroup\$
3
  • \$\begingroup\$ Trivia: Java's SynchronousQueue<E> is even more extreme: " A synchronous queue does not have any internal capacity, not even a capacity of one." \$\endgroup\$ Commented Nov 10, 2015 at 17:04
  • \$\begingroup\$ @Mat That is a weird "container". I guess if OP was trying to produce that one, my main comment is moot. \$\endgroup\$ Commented Nov 10, 2015 at 17:09
  • \$\begingroup\$ @Barry thanks a lot for your helpful comments! Yes I actually had a real queue in the beginning, but I changed it. The reason is that the producer is much faster than the worker getting the images, so if I have a queue, I get more and more latency and in the end the application runs out of memory. This way, I make sure that the producer and the consumer are in "sync", processing images at the same speed, and can at the same time still exploit the speedup of multithreading. \$\endgroup\$ Commented Nov 11, 2015 at 11:04

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.