Reputation: 1299
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
Upvotes: 3
Views: 1772
Reputation: 15524
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.
Upvotes: 2
Reputation: 13411
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.
Upvotes: 1