3

My program reads in two input files and alternates writing lines to an output file. I have it so it writes in the right order (first file, then second , then first again, ....) but the problem is it writes the last character in each file twice at the end.

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

input 1

a
c
e

input 2

b
d
f

output

abcdefef

I don't know why it writes the last character again, also is there a better way to do the ordering part using threads, I understand that a mutex is used to make sure both threads don't write at the same time, however how can I guarantee that thread one executes before thread 2 and make sure it keeps alternating?
Thanks

Greg Brown
  • 1,227
  • 2
  • 20
  • 44

2 Answers2

2

The correct and idiomatic way of reading from an std::ifstream until EOF is reached is:

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

Compared to this. Assume all but last characters have already been read from stream:

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 */
}

Secondly, your function print_line has some problems regarding synchronization. One way to solve it is to use a std::condition_variable. Here's an example:

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.
    }
}

As you see in the example above I have also passed the streams as references instead of pointers. Passing pointers is error prone as it is possible to pass nullptr (NULL-value).

To pass the streams as references to the constructor of std::thread you must wrap them in a reference wrapper std::ref, e.g. like this: (ctor of std::thread copies the arguments)

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

Live example (slightly modified to use standard IO instead of fstream)


Some other things:

1. Unnecessary code in 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");

Use the constructor taking the filename directly here instead of using open:

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

2. Use idiomatic way to check if stream is ready for reading:

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

3. Unnecessary to use close in this case, as the dtor will close the resources (rely on RAII).

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

4. Your design is somewhat poor as further accesses to any of the streams or turn in the main function will result in a data race.

(5.) Prefer to not pollute the namespace with using namespace std, instead use fully qualified names (e.g. std::ifstream) everywhere. Optionally declare using std::ifstream etc. inside relevant scope.

Felix Glas
  • 15,065
  • 7
  • 53
  • 82
  • That fixed the problem with reading the extra character, however now I don't consistently get abcdef, sometimes get them out of order or just the first letter of input 1. Before when I had it the other way I consistently got abcdefef. Any clue to as why? – Greg Brown Nov 27 '13 at 22:03
  • Thanks so much for your answer everything works great, just do you mind expanding on cv.wait(lock, [&t] { return turn == t; }); I know what condition variables do I just never seen them with [&t] { return turn == t; } that argument in them. – Greg Brown Nov 27 '13 at 23:59
  • @GregBrown the second (optional) parameter to `std::condition_variable::wait` is a predicate that must return `true` or `false` according to the condition to wait for. Threads can wake up spuriously, even when no thread has signaled the condition variable, which is why it is necessary to always verify the waiting condition after waking up/notification. You usually do this with a loop, e.g. `while (!cond) { cv.wait() }`. This ensures that if the condition has not yet been reached then the thread will resume waiting. – Felix Glas Nov 28 '13 at 01:17
  • @GregBrown The overload with the predicate callable `cv.wait(lock, [&t] { return turn == t; })` is equivalent to `while(turn != t) { cv.wait(lock); }`. That is the thread will wait until the predicate returns true i.e. `turn == t`. [See this for more info](http://en.cppreference.com/w/cpp/thread/condition_variable/wait). The expression `[&t] {/*...*/}` is a simple [lambda expression](http://stackoverflow.com/questions/7627098/what-is-a-lambda-expression-in-c11) capturing a reference to `t`. – Felix Glas Nov 28 '13 at 01:20
1

Regarding the EOF: There's a fairly good explanation over here: How does ifstream's eof() work?

Regarding the lock: Lock only for the time you do the output, to reduce lock contention and switching the turn variable.

Besides that, it is a somwewhat horrible design in my opinion. I'm not even sure, if a C++ stream can be used that way across threads, but even then I would doubt that it is a good idea.

Community
  • 1
  • 1
JensG
  • 13,148
  • 4
  • 45
  • 55