【问题标题】:Reading two files and outputting to another file using threads使用线程读取两个文件并输出到另一个文件
【发布时间】:2013-11-27 19:51:30
【问题描述】:

我的程序读入两个输入文件,并交替向输出文件写入行。我有它,所以它以正确的顺序写入(第一个文件,然后是第二个,然后是第一个,......)但问题是它在每个文件的最后一个字符写入两次。

#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
using namespace std;


mutex mtx;
int turn = 1;

void print_line(ifstream * in_stream, ofstream * out_stream, int t);

int main(int argc, const char * argv[]){
    ifstream input_file_1;
    ifstream input_file_2;
    ofstream output_file;

    input_file_1.open("input_1");
    input_file_2.open("input_2");
    output_file.open("output");

    if (input_file_1.fail() || input_file_2.fail() || output_file.fail()) {
        cout << "Error while opening the input files\n";
        exit(EXIT_FAILURE);
    }
    else{
        thread input1 (print_line, &input_file_1, &output_file, 1);
        thread input2 (print_line, &input_file_2, &output_file, 2);
        input1.join();
        input2.join();
    }
    input_file_1.close();
    input_file_2.close();
    output_file.close();

    return 0;
}

void print_line(ifstream * in_stream, ofstream * out_stream, int t){
    char temp;
    while (!in_stream->eof()) {
        mtx.lock();
        if (turn == t) {
            *in_stream>>temp;
            *out_stream<<temp;
            if (turn == 1) {
                turn = 2;
            }
            else{
                turn = 1;
            }
        }
        mtx.unlock();
    }
} 

输入 1

a
c
e

输入 2

b
d
f

输出

abcdefef

我不知道为什么它会再次写入最后一个字符,还有更好的方法来使用线程进行排序部分,我知道使用互斥锁来确保两个线程不会同时写入,但是如何保证线程 1 在线程 2 之前执行并确保它保持交替?
谢谢

【问题讨论】:

    标签: c++ multithreading


    【解决方案1】:

    std::ifstream 读取直到到达 EOF 的正确和惯用方式是:

    char temp;
    while(in_stream >> temp) {
        // Will only be entered if token could be read and not EOF.
    }
    

    与此相比。 假设除了最后一个字符之外的所有字符都已从流中读取

    while(!in_stream.eof()) {
        in_stream >> temp; // 1st iteration: reads last character, STILL NOT EOF.
                           // 2nd iteration: tries to read but reaches EOF.
                           //                Sets eof() to true. temp unchanged.
                           //                temp still equal to last token read.
                           //                Continue to next statement...
        /* More statements */
    }
    

    其次,您的函数print_line 在同步方面存在一些问题。解决它的一种方法是使用std::condition_variable。这是一个例子:

    condition_variable cv;
    
    void print_line(ifstream& in_stream, ofstream& out_stream, int t){
        char temp;
        while (in_stream >> temp) {
            unique_lock<mutex> lock(mtx); // Aquire lock on mutex.
    
            // Block until notified. Same as "while(turn!=t) cv.wait(lock)".
            cv.wait(lock, [&t] { return turn == t; });
            out_stream << temp;
            turn = (turn == 1) ? 2 : 1;
            cv.notify_all(); // Notify all waiting threads.
        }
    }
    

    正如您在上面的示例中看到的,我还将流作为引用而不是指针传递。传递指针很容易出错,因为可以传递nullptr(NULL 值)。

    要将流作为引用传递给std::thread 的构造函数,您必须将它们包装在引用包装器std::ref 中,例如像这样:(std::thread 的 ctor 复制参数)

    thread input1(print_line, ref(input_file_1), ref(output_file), 1);
    

    Live example(稍作修改以使用标准 IO 代替 fstream


    其他一些事情:

    1.main中的不必要代码:

    ifstream input_file_1;
    ifstream input_file_2;
    ofstream output_file;
    
    input_file_1.open("input_1");
    input_file_2.open("input_2");
    output_file.open("output");
    

    在此处使用直接获取文件名的构造函数,而不是使用open

    ifstream input_file_1("input_1");
    ifstream input_file_2("input_2");
    ofstream output_file("output");
    

    2. 使用惯用的方式检查流是否准备好读取:

    if (!input_file_1 || !input_file_2 || !output_file) {
    

    3. 在这种情况下不需要使用close,因为 dtor 将关闭资源(依赖于 RAII)。

    input_file_1.close(); // \
    input_file_2.close(); //  } Unnecessary
    output_file.close();  // /
    

    4.您的设计有些糟糕,因为进一步访问main 函数中的任何流或turn 将导致数据竞争。

    (5.) 不要使用using namespace std 污染命名空间,而是在任何地方使用完全限定名称(例如std::ifstream)。可选择在相关范围内声明 using std::ifstream 等。

    【讨论】:

    • 这解决了读取额外字符的问题,但是现在我并没有始终如一地得到 abcdef,有时让它们乱序或只是输入 1 的第一个字母。在我拥有另一个之前我一直得到 abcdefef 的方式。任何线索为什么?
    • 非常感谢您的回答一切都很好,只是您介意扩展 cv.wait(lock, [&t] { return turn == t; });我知道什么条件变量我只是从未见过 [&t] { return turn == t; } 中的那个论点。
    • @GregBrown std::condition_variable::wait 的第二个(可选)参数是一个谓词,必须根据等待的条件返回truefalse。即使没有线程向条件变量发出信号,线程也可能被虚假唤醒,这就是为什么必须在唤醒/通知后始终验证等待条件的原因。您通常使用循环来执行此操作,例如while (!cond) { cv.wait() }。这确保了如果条件尚未达到,那么线程将继续等待。
    • @GregBrown 带有可调用谓词cv.wait(lock, [&amp;t] { return turn == t; }) 的重载等价于while(turn != t) { cv.wait(lock); }。那就是线程将等到谓词返回真,即turn == tSee this for more info。表达式[&amp;t] {/*...*/} 是一个简单的lambda expression 捕获对t 的引用。
    【解决方案2】:

    关于 EOF:这里有一个很好的解释:How does ifstream's eof() work?

    关于锁:仅在您执行输出时锁定,以减少锁争用和切换turn 变量。

    除此之外,在我看来,这是一个有点可怕的设计。我什至不确定是否可以跨线程使用 C++ 流,但即便如此,我也会怀疑这是一个好主意。

    【讨论】:

      猜你喜欢
      • 2017-05-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-12-09
      • 1970-01-01
      • 1970-01-01
      • 2017-03-05
      相关资源
      最近更新 更多