2

this function takes a ofstream as a reference, then builds struct packets and threads off the structs with the ofstream to a trie matching class. A stack is returned with n matches in order of match distance. The ofstream, packet, and stack are then passed by reference to a print function that writes the matches to the same output file - waiting for when the ofstream is not in use. The problem is the ofstream crashes after half the matches are written.

The ofstream, packet, and outfile header

void Match_Import_Start(Trie & tri)
{
    stack<Trie::MatchesT> out;
    ofstream myfile;
    Trie::MatchesT matchSet;

    myfile.open(outFile.c_str() );
    myfile << "DESCRIPTION,SUGGESTED.DESCRIPTION,EDIT" << "\n"; //write header
    myfile.close();

    Match_Import (tri, myfile, out, matchSet);

    return;
}

spawn threads from record list

void Match_Import(Trie &tri, ofstream &myfile, stack<Trie::MatchesT> out, Trie::MatchesT matchSet)
{   
    out=parse_CSV_file(timeFile); //read in records

    settingT x;

    x.score=0;

    boost::thread_group tgroup; //http://stackoverflow.com/questions/8744279/create-threads-in-a-loop
    while (!out.empty() ) {
        matchSet=out.top();
        out.pop();

        tgroup.create_thread(boost::bind( MaxDistanceCorrections, boost::ref(tri), matchSet, boost::ref(myfile), boost::ref(x) ) );
    }
    tgroup.join_all();

    return;
}

check for valid return from match

void MaxDistanceCorrections(Trie & tri, Trie::MatchesT matchSet, ofstream &myfile, settingT &x)
{
    if (!matchSet.candidateStack.empty() ) ) {
        matchSet.candidateStack.sort(compareCorrMain);
        PrintCorrections(tri, matchSet, myfile, x);
        return;

    } else {        
        tri.suggest(matchSet); //send out to trie match

         if (matchSet.candidateStack.empty() ) { }// modify match parameters

        MaxDistanceCorrections(tri, matchSet, myfile, x);
    }
}

and print when ofstream is available

void PrintCorrections(Trie &tri, Trie::MatchesT &matchSet, ofstream &myfile, settingT &x)
{   
    while (true) {
        if (!myfile.is_open() ) { 
          myfile.open(outFile.c_str(), ios::out | ios::app);
          break;
        }  
     }

    while (!matchSet.candidateStack.empty() ) {
        Trie::CorrectionT corr=matchSet.candidateStack.back();
        matchSet.candidateStack.pop_back();

        const bool flagGood=scoreSuggest (corr); //score
        if (flagGood ) x.score++;

        myfile << matchSet.testCase << "," << corr.match << "," << corr.editDistance << "\n";

    }
    myfile.close();

    return;
}

Fairly new at mutithreading, these functions worked fine as a single thread.

Should the check for ofstream available be placed within the while loop that spins off the candidate matches? Once the ofstream is available then starting the print sequence should tie-up the ofstream form other threads.

Is there a better way to reserve use of an ofstream shared by multiple threads?

forest.peterson
  • 755
  • 2
  • 13
  • 30

2 Answers2

3

If you are new to multi-threading then let this be a lesson in why you should use mutex locks.

The fstream object is just that - an object. You must protect it against simultaneous access by using a mutex.

If you just want threads to be able to write information to a file, you could instead pass a file name (as a string) instead of an fstream. The thread could then open the file with exclusive read/write access. This would use a local fstream object, and the locking would be handled by the operating system.

paddy
  • 60,864
  • 6
  • 61
  • 103
  • Or rather, some form of lock. A Windows mutex would be overkill for this :) – Billy ONeal Mar 20 '13 at 01:25
  • Yep, I didn't say 'Windows'. I didn't see anything in the question that mentioned a Windows target. I use the term *mutex* to refer to a type of lock. In Windows, there are two mutex implementations that I know of (Critical Section and Mutex), which each have their uses. – paddy Mar 20 '13 at 01:32
  • I understand that. What I'm saying is the word "mutex" means something else in other contexts which may be confusing to someone who is new. – Billy ONeal Mar 20 '13 at 01:34
  • @paddy I will give the filename a try rather than pass the ofstream reference – forest.peterson Mar 20 '13 at 01:57
  • @BillyONeal No worries, and it's a fair comment. Hopefully the terminology is not confusing to someone who is using Boost =) – paddy Mar 20 '13 at 02:07
  • @forest.peterson You will still have to make sure you have disabled sharing. I could have made a better suggestion: Make a simple class that wraps a `mutex` and a `fstream`. Have a member function on it that writes a single entry to the file. It must acquire a lock on the mutex before doing this (and release afterwards). That way you will not have the race conditions that Billy mentioned in his answer, and you will not have to worry about doing the locking in your threads. – paddy Mar 20 '13 at 02:10
  • @paddy I tried the lock as `outfile.open(outFile.c_str(), ios::out | ios::app, _SH_DENYRW);` based on http://msdn.microsoft.com/en-us/library/8f30b0db.aspx but the result is similar, there was not evidence of the race condition described by BillyONeal like there was before - but the output terminated early - 307 of 631 records out - the same code toggled back to single thread works fine. Also, debugging in VS2008 and later will use g++ for linux + don't let boost decive, I know nearly nothing :) – forest.peterson Mar 20 '13 at 03:16
  • with ofstream passed as a reference - rather than opening a new ofstream each time using the same file name - following http://www.boost.org/doc/libs/1_53_0/doc/html/thread/synchronization.html `boost::mutex` works with `mtx_.lock();` and `mtx_.unlock();` placed before and after the print loop; the MT runtime improvement is 20% over ST; also, I have no idea why this works... – forest.peterson Mar 20 '13 at 03:56
2

This code exhibits undefined behavior from multiple threads. See N3485 27.2.3 [iostreams.threadsafety]/1:

Concurrent access to a stream object (27.8, 27.9), stream buffer object (27.6), or C Library stream (27.9.2) by multiple threads may result in a data race (1.10) unless otherwise specified (27.4). [ Note: Data races result in undefined behavior (1.10). —end note ]

In the general case, streams are not safe to use across threads. You must protect the stream using a lock, such as std::mutex.

Note that even if streams were safe to access across threads this code would probably not do what you want. Consider this line:

myfile << matchSet.testCase << "," << corr.match << corr.editDistance << "\n";

which is the same as

myfile << matchSet.testCase;
myfile << ",";
myfile << corr.match;
myfile << corr.editDistance;
myfile << "\n";

Note the race condition. Let's say that your implementation's operator<< for streams is synchronized for you by the implementation. You still have a potential race in this outer code. For instance, here is one possible execution of this across 2 threads:

   Thread 1                              Thread 2
======================================================================
myfile << matchSet.testCase;
                                         myfile << matchSet.testCase;
myfile << ",";
                                         myfile << ",";
myfile << corr.match;
myfile << corr.editDistance;
myfile << "\n";
                                         myfile << corr.match;
                                         myfile << corr.editDistance;
                                         myfile << "\n";

Instead of getting that to write as a single line, you'll get output from each thread mixed up together, resulting in gibberish.

Billy ONeal
  • 104,103
  • 58
  • 317
  • 552
  • that was a typo in editing for the post, it is myfile << matchSet.testCase << "," << corr.match << "," << corr.editDistance << "\n"; - your race condition is exactly what happens when it crashes - I can see the garbled output in the outfile. But when one thread opens the file then the other thread should see it as open and pause in the while loop? – forest.peterson Mar 20 '13 at 01:59
  • @forest: No. Why would you expect it to pause? Those are 5 completely separate IO operations; they may be interleaved in any order (unless you use locks to enforce otherwise). As for the `is_open` check, if your streams implementation is not synchronized, there is no guarantee that opening the file on one thread will update the state and flush CPU caches and such for a thread running on a different CPU. It'd be very easy for both threads to end up inside the `if (file.is_open())` check, for instance. – Billy ONeal Mar 20 '13 at 02:21
  • @Billy_ONeal yes, I understand that intuition and common sense has no place in c++ but, if I open an ofstream then pass references to it down a dozen paths, any change on each of those paths is referencing the same ofstream, so it seems like in this sense, as far as each thread is concerned they are one thread. If thread 1 has ofstream xyz open then thread 12 is referencing the exact same ofstream xyz; so no need to update or flush anything. Pragamtically this logic is clearly wrong, but it seemed logical... – forest.peterson Mar 20 '13 at 02:46
  • @forest: That's not a C++ thing, that's a parallelism in general thing. Why would you expect 5 completely independent function calls to be executed as an atomic operation without any form of synchronization? Why would you expect mutable, shared data (such as ostream's format or error condition flags) to be thread safe without synchronization? I know of no language which would obey your asserted "logic." – Billy ONeal Mar 20 '13 at 04:23
  • @Billy_ONeal: I am confused now. Why does threading overrule referencing? – forest.peterson Mar 20 '13 at 04:31
  • Also, the myfile pause should have had a while loop around it – forest.peterson Mar 20 '13 at 04:39
  • @forest: References are aliases. They don't know or care about threading. Making a reference to something doesn't copy it. You can have 10 references to the same object, but they're all aliases to the same underlying object. Accessing that object concurrently is still a data race, regardless of the number of indirections you add. – Billy ONeal Mar 20 '13 at 06:21
  • OK, if "Accessing that object concurrently is still a data race" then checking if another thread is accessing that object and waiting until it is not being accessed should work to prevent concurrent accessing - but it doesn't; you almost have explained this but I am still a step away from understanding the why and how. Should we open a new question for this? – forest.peterson Mar 20 '13 at 12:57
  • @forest: I don't see anywhere in this code where you're "checking if another thread is accessing that object." You may want to watch this: http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2 and this: http://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-2-of-2 – Billy ONeal Mar 20 '13 at 17:41