[英]Single producer-multiple consumers: How to tell consumers that production is complete
在我的程序中,生产者线程从文本文件(具有约8000行文本)读取文本行,并将这些行加载到并发队列中。
三个使用者线程从队列中读取行,每个线程都写入一个单独的文件。
当我运行程序时,仅生产者线程和消费者线程之一完成。 其他两个线程似乎挂起。
如何可靠地告诉所有使用者线程已经到达文件末尾,因此它们应该返回,但要确保队列完全为空。
我的平台是Windows 7 64位
VC11。
编译为64位和32位的代码具有相同的行为。
这是代码。 (它是独立的并且可编译)
#include <queue>
#include<iostream>
#include<fstream>
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>
#include<string>
#include<memory>
template<typename Data>
class concurrent_queue
{
private:
std::queue<Data> the_queue;
mutable std::mutex the_mutex;
std::condition_variable the_condition_variable;
public:
void push(Data const& data){
{
std::lock_guard<std::mutex> lock(the_mutex);
the_queue.push(data);
}
the_condition_variable.notify_one();
}
bool empty() const{
std::unique_lock<std::mutex> lock(the_mutex);
return the_queue.empty();
}
const size_t size() const{
std::lock_guard<std::mutex> lock(the_mutex);
return the_queue.size();
}
bool try_pop(Data& popped_value){
std::unique_lock<std::mutex> lock(the_mutex);
if(the_queue.empty()){
return false;
}
popped_value=the_queue.front();
the_queue.pop();
return true;
}
void wait_and_pop(Data& popped_value){
std::unique_lock<std::mutex> lock(the_mutex);
while(the_queue.empty()){
the_condition_variable.wait(lock);
}
popped_value=the_queue.front();
the_queue.pop();
}
};
std::atomic<bool> done(true);
typedef std::vector<std::string> segment;
concurrent_queue<segment> data;
const int one_block = 15;
void producer()
{
done.store(false);
std::ifstream inFile("c:/sample.txt");
if(!inFile.is_open()){
std::cout << "Can't read from file\n";
return;
}
std::string line;
segment seg;
int cnt = 0;
while(std::getline(inFile,line)){
seg.push_back(line);
++cnt;
if( cnt == one_block ){
data.push( seg );
seg.clear();
cnt = 0;
}
}
inFile.close();
done.store(true);
std::cout << "all done\n";
}
void consumer( std::string fname)
{
std::ofstream outFile(fname.c_str());
if(!outFile.is_open()){
std::cout << "Can't write to file\n";
return;
}
do{
while(!data.empty()){
segment seg;
data.wait_and_pop( seg );
for(size_t i = 0; i < seg.size(); ++i)
{
outFile << seg[i] << std::endl;
}
outFile.flush();
}
} while(!done.load());
outFile.close();
std::cout << fname << " done.\n";
}
int main()
{
std::thread th0(producer);
std::thread th1(consumer, "Worker1.txt");
std::thread th2(consumer, "Worker2.txt");
std::thread th3(consumer, "Worker3.txt");
th0.join();
th1.join();
th2.join();
th3.join();
return 0;
}
我用来终止等待队列中所有线程的方法是在队列上有一个标志,指出是否已完成,并在检查pop()
函数中是否包含元素之前对其进行了测试。 如果该标志指示该程序应停止,则在队列中没有任何元素的情况下,任何调用pop()
线程都会引发异常。 更改标志时,更改线程仅在相应的条件变量上调用notify_all()
。
看下面的代码:
while(!data.empty()){
segment seg;
data.wait_and_pop( seg );
...
考虑要读取最后一段数据的情况。 消费者th1
和th2
正在等待读取数据。
使用者th1
检查!data.empty()
并发现有要读取的数据。 然后,在th1
调用data.wait_and_pop()
,使用者th2
检查!data.empty()
并发现它为true。 假设消费者th1
消耗了最后一个细分。 现在,由于没有要读取的段,因此th2
无限期地等待the_queue.empty()
中的data.wait_and_pop()
。
试试下面的代码代替上面的代码:
segment seg;
while(data.try_pop(seg)){
...
应该让它工作。
你可能想添加一个布尔标志, concurrent_queue
。 读取文件后将其设置(在互斥锁下)。 读取文件且队列为空后,请使用notify_all
从清空队列的使用者中广播条件变量。
这将唤醒所有其他使用者,这些使用者需要发现最终条件(标志设置和队列为空)并退出其循环。 为了避免比赛条件,这意味着他们需要首先检查相同的组合条件。
现有标志的问题在于,永远不会从等待condvar唤醒的线程,永远不会对其进行检查。 “完成”标志需要成为他们正在等待的状态的一部分。
[编辑:Dietmar对标志的不同含义可能会导致代码更简单,但是我没有将它们都写成比较。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.