简体   繁体   English

单一生产者-多个消费者:如何告诉消费者生产已经完成

[英]Single producer-multiple consumers: How to tell consumers that production is complete

I my program a producer thread reads lines of text from a text file ( have about 8000 lines of text) and loads the lines into a concurrent queue. 在我的程序中,生产者线程从文本文件(具有约8000行文本)读取文本行,并将这些行加载到并发队列中。

Three consumer threads read the lines from the queue each writing to a separate file. 三个使用者线程从队列中读取行,每个线程都写入一个单独的文件。

When I run the program only the producer thread and only one of the consumer threads complete. 当我运行程序时,仅生产者线程和消费者线程之一完成。 The other two threads seem to hang. 其他两个线程似乎挂起。

How do I reliably tell all consumer threads that the end of file has been reached so they should return but making sure the queue is completely empty. 如何可靠地告诉所有使用者线程已经到达文件末尾,因此它们应该返回,但要确保队列完全为空。

My platform is Windows 7 64-bit 我的平台是Windows 7 64位

VC11. VC11。

Code compiled as 64-bit and 32-bit got the same behavior. 编译为64位和32位的代码具有相同的行为。

Here is the code. 这是代码。 (It is self-contained and compilable) (它是独立的并且可编译)

#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;
}

The approach I'm using to terminate all threads waiting on a queue is to have a flag on the queue stating whether it is done which is tested before checking that there are element in the pop() function. 我用来终止等待队列中所有线程的方法是在队列上有一个标志,指出是否已完成,并在检查pop()函数中是否包含元素之前对其进行了测试。 If the flag indicates that the program should stop, any thread calling pop() throws an exception if there are no elements in the queue. 如果该标志指示该程序应停止,则在队列中没有任何元素的情况下,任何调用pop()线程都会引发异常。 When the flag is changed, the changing thread just calls notify_all() on the corresponding condition variable. 更改标志时,更改线程仅在相应的条件变量上调用notify_all()

Look at the following code: 看下面的代码:

while(!data.empty()){
    segment seg;
    data.wait_and_pop( seg );
    ...

Consider a situation where the last segment of data is to be read. 考虑要读取最后一段数据的情况。 And comsumers th1 & th2 are waiting for data to be read. 消费者th1th2正在等待读取数据。

Consumer th1 checks for !data.empty() and finds there is data to be read. 使用者th1检查!data.empty()并发现有要读取的数据。 Then before th1 calls data.wait_and_pop() , consumer th2 checks for !data.empty() and finds it to be true. 然后,在th1调用data.wait_and_pop() ,使用者th2检查!data.empty()并发现它为true。 Assume comsumer th1 consumes the last segment. 假设消费者th1消耗了最后一个细分。 Now, since there is no segment to be read, th2 waits indefinitely on the_queue.empty() in data.wait_and_pop() . 现在,由于没有要读取的段,因此th2无限期地等待the_queue.empty()中的data.wait_and_pop()

Try this code instead of the snippet above: 试试下面的代码代替上面的代码:

segment seg;
while(data.try_pop(seg)){
    ...

Should get it working. 应该让它工作。

You probably want to add a boolean flag to concurrent_queue . 你可能想添加一个布尔标志, concurrent_queue Set it (under the mutex) once the file is read. 读取文件后将其设置(在互斥锁下)。 Once the file is read and the queue is empty, broadcast the condition variable from the consumer that emptied the queue using notify_all . 读取文件队列为空后,请使用notify_all从清空队列的使用者中广播条件变量。

This will wake up all the other consumers, which need to spot the final condition (flag set and queue empty) and quit their loop. 这将唤醒所有其他使用者,这些使用者需要发现最终条件(标志设置和队列为空)并退出其循环。 To avoid the race condition, that means they need to check the same combined condition before waiting in the first place. 为了避免比赛条件,这意味着他们需要首先检查相同的组合条件。

The problem with your existing flag is that threads that never wake out of waiting for the condvar, never check it. 现有标志的问题在于,永远不会从等待condvar唤醒的线程,永远不会对其进行检查。 The "finished" flag needs to be part of the state that they're waiting for. “完成”标志需要成为他们正在等待的状态的一部分。

[Edit: Dietmar's subtly different meaning for the flag probably results in simpler code, but I haven't written them both to compare.] [编辑:Dietmar对标志的不同含义可能会导致代码更简单,但是我没有将它们都写成比较。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM