Alex
Alex

Reputation: 67

Basic thread locking in C++11

How do I lock my thread so that my output isn't something like this: hello...hello...hheelhllelolo.l..o......

std::size_t const nthreads=5;
std::vector<std::thread> my_threads(nthreads);

for(unsigned i=0;i<nthreads;i++)
{
    my_threads[i] = std::thread ([] () {std::cout << "hello...";}); 
}

Upvotes: 1

Views: 10271

Answers (2)

bames53
bames53

Reputation: 88225

The standard says:

Concurrent access to a synchronized (27.5.3.4) standard iostream object’s formatted and unformatted input (27.7.2.1) and output (27.7.3.1) functions or a standard C stream by multiple threads shall not result in a data race (1.10). [Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. — end note]
                                                                                        — [iostream.objects.overview] 27.4.1 p4

Notice that the requirement not to produce a data race applies only to the standard iostream objects (cout, cin, cerr, clog, wcout, wcin, wcerr, and wclog) and only when they are synchronized (which they are by default and which can be disabled using the sync_with_stdio member function).

Unfortunately I've noticed two phenomena; implementations either provide stricter guarantees than required (e.g., thread synchronization for all stream objects no matter what, giving poor performance) or fewer (e.g., standard stream objects that are sync_with_stdio produce data races). MSVC seems to lean toward the former while libc++ leans toward the latter.

Anyway, as the note indicates, you have to provide mutual exclusion yourself if you want to avoid interleaved characters. Here's one way to do it:

std::mutex m;

struct lockostream {
    std::lock_guard<std::mutex> l;
    lockostream() : l(m) {}
};

std::ostream &operator<<(std::ostream &os, lockostream const &l) {
    return os;
}

std::cout << lockostream() << "Hello, World!\n";

This way a lock guard is created and lives for the duration of the expression using std::cout. You can templatized the lockostream object to work for any basic_*stream, and even on the address of the stream so that you have a seperate mutex for each one.

Of course the standard stream objects are global variables, so you might want to avoid them the same way all global variables should be avoided. They're handy for learning C++ and toy programs, but you might want to arrange something better for real programs.

Upvotes: 9

Stephan Dollberg
Stephan Dollberg

Reputation: 34608

You have to use the normal locking techniques as you would do with any other resource otherwise you are experiencing UB.

std::mutex m;
std::lock_guard<std::mutex> lock(m);
std::cout << "hello hello";

or alternativly you can use printf which is threadsafe(on posix):

printf("hello hello");

Upvotes: 3

Related Questions