我想通过资源有限的中间“工人”线程对通常的生产者 - 消费者问题进行略微修改。示例应用程序可以是:
我不是说这是解决此类问题的好方法,但它突出了我正在努力解决的问题,即如何正确地通知工作者和消费者线程。
我有一个带有以下界面的线程安全队列:
template<class T>
class threadsafe_queue
{
public:
threadsafe_queue() {}
threadsafe_queue(const threadsafe_queue& other);
void push(T new_value);
void wait_and_pop(T& value);
std::shared_ptr<T> wait_and_pop();
bool try_pop(T& value);
std::shared_ptr<T> try_pop();
bool empty() const;
};
我用单个工作线程解决问题的第一个想法是使用两个原子bool,如下所示:
#include <chrono>
#include <thread>
void queue_producer(threadsafe_queue<unsigned>& queue, std::atomic<bool>& producer_finished)
{
for (unsigned i = 0; i < 10; ++i) {
queue.push(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
producer_finished.store(true);
std::cout << "Producer finished." << std::endl;
}
void queue_processor(threadsafe_queue<unsigned>& in_queue, threadsafe_queue<unsigned>& out_queue,
std::atomic<bool>& producer_finished, std::atomic<bool>& processor_finished)
{
unsigned value;
while (!producer_finished.load()) {
in_queue.wait_and_pop(value);
value *= 10;
out_queue.push(value);
}
processor_finished.store(true);
std::cout << "Processor finished." << std::endl;
}
void queue_consumer(threadsafe_queue<unsigned>& queue, std::atomic<bool>& processor_finished)
{
unsigned value;
while (!processor_finished.load()) {
queue.wait_and_pop(value);
std::cout << "Received value " << value << "." << std::endl; // Or write to file etc.
}
std::cout << "Consumer finished." << std::endl;
}
int main(int argc, const char * argv[])
{
std::atomic<bool> producer_finished(false);
std::atomic<bool> processor_finished(false);
threadsafe_queue<unsigned> in_queue;
threadsafe_queue<unsigned> out_queue;
std::thread producer_thread(queue_producer, std::ref(in_queue), std::ref(producer_finished));
std::thread processor_thread(queue_processor, std::ref(in_queue), std::ref(out_queue), std::ref(producer_finished), std::ref(processor_finished));
std::thread consumer_thread(queue_consumer, std::ref(out_queue), std::ref(processor_finished));
producer_thread.join();
processor_thread.join();
consumer_thread.join();
return 0;
}
这个问题是处理器(和消费者)可以在设置原子bool之前重新进入while循环,因此无限期地等待永远不会出现的记录。
我还认为解决方案可能是将某种类型的sentinel值推送到队列以表示结束(可以使用包装类),但这似乎并不是一种特别好的方法,并且它不适用于多工作者版本。我实际上认为多工作者版本是一个更加困难的问题,因此对单一工作者版本的任何帮助都是一个很好的开始。
答案 0 :(得分:0)
您可以将<condition_variable>
与通知原则一起考虑<mutex>
。
你需要某个地方:
std::mutex mtx; // for locking
std::condition_variable ready; // for waiting conditions
在生产者方面,您处理输入并保护队列的更新:
{
std::lock_guard<std::mutex> guard(mtx); // lock
// ... updated the queue
ready.notify_all(); // notify consumers
} // the lock is removed here
在消费者方面,你有一个循环:
{
std::unique_lock<std::mutex> guard(mtx); // lock
// wait condition
ready.wait(guard, [&]() {return /* queue not empty */; }); // lock is released as long as condition is false
//... here lock is set and condition is true: read element from queue
//... if processing is long release the lock when queue is read
} // otherwhise the lock is removed here
您可以找到包含条件变量生产者/消费者示例here的教程。请注意notify_one()
与notify_all()
;我目前正在尝试它,据我所知,第一个是生产者/非专业化消费者的最佳选择。如果每个消费者都是专业的,那么后者是适当的,即必须知道他是否能够处理输入。
其他方法
使用您当前的代码。我得到的线条&#34;收到&#34;显示从10到90,显示所有线程已贡献,但程序挂起。
如果我用this_thread::yield()
替换等待以避免不必要的等待,所有三个线程立即声明他们已经完成了他们的工作。为什么?因为生产者运行得更快,当处理器有机会工作时,producer_finished
是真的。然后处理器甚至不运行循环。
因此,您的代码严重依赖于执行顺序。请记住,您的队列是线程安全的,您可以通过修改处理器和使用者的while条件来改善情况,如下所示:
while (!producer_finished.load() || !in_queue.empty()) {
...
while (!processor_finished.load() || !queue.empty()) {
...
来自上述条件变量方法的公寓,另一种方法可能是使用std::promise<T>
(例如:处理器发送通知)和std::shared_future<T>
(例如:让消费者知道他有什么可以处理的)
答案 1 :(得分:0)
您可以使用名为Message Box(也称为Mailbox)的其中一个线程同步基元将Producer和Consumer线程链接在一起。可以使用C ++ 11 Mutex和Condition Variable实现邮箱。
邮箱提供了两个线程交换信息的方法。通常,一个线程将生成消息并发送到另一个线程进行处理。邮件包含在邮箱中,因此线程安全。
<强>生产者强>
mbox.Put(message);
尝试在指定的邮箱中放置邮件。如果邮箱已满,则呼叫可能会阻止或不依赖于设计。
<强>消费强>
message = mbox.Get(); // Blocking
在指定邮箱可用时删除邮件,并返回邮件的地址或移动副本。
Put和Get方法的代码段:
/// <summary>Push an item to the back of the queue. Move.</summary>
void Put(T && itemToAddByMoving)
{
{
std::lock_guard<std::mutex> lg(m_queueMutex);
m_deque.push_back(std::forward<T>(itemToAddByMoving)); // Perfect forwarding: rvalue if argument is rvalue
} // release lock
m_queueCondVar.notify_one(); // Notify consumer thread that there is data available in the queue.
}
/// <summary>Waiting for the queue to have data ready (not empty). Pop the first element when ready.</summary>
/// <returns>First element in front of the queue.</returns>
T Get()
{
T poppedValue; // T should support move.
{
std::unique_lock<std::mutex> ul(m_queueMutex);
m_queueCondVar.wait(ul, [&]{ return !m_deque.empty(); }); // Wait here. Blocking.
poppedValue = m_deque.front();
m_deque.pop_front();
} // release lock
return poppedValue;
}