Locking C++ streams for multi-threaded code

Let’s start by introducing a fun program which has a slight defect (can you spot it?):

#include <future>
#include <vector>
#include <iostream>

using namespace std;

void print(int n) {
    cout << "This is line " << n << '\n';
}

int main() {
    vector<future<void>> tasks;
    for (int i{}; i != 10; ++i) {
        tasks.push_back(async(launch::async, &print, i + 1));
    }
}

When run (under Windows 10) it produced the following output for a sample run:

This is line 1This is line 2
This is line
This is line 43
This is line 5

This is line This is line 6
This is line 87
This is line 9
This is line
10

The problem is that while the global streams are thread-safe in Modern C++, accessing them from different threads can cause interleaved output. One way of solving this is to create (custom) manipulators which operate on a global std::mutex, locking it at the beginning of a stream output sequence and unlocking it at the end. Writing these new manipulators on the same line, we get:

cout << lock << "This is line " << n << '\n' << unlock;

To make this work, we need two functions which take and return a reference to a std::ios_base. (This is how the parameterless manipulators of the Standard Library are defined.) Here is a program which shows this in action:

#include <mutex>
#include <future>
#include <ios>
#include <vector>
#include <iostream>

using namespace std;

mutex ostream_mutex{};

ios_base& lock(ios_base& strm) {
    ostream_mutex.lock();
    return strm;
}

ios_base& unlock(ios_base& strm) {
    ostream_mutex.unlock();
    return strm;
}

void print(int n) {  // note: using lock and unlock
    cout << lock << "This is line " << n << '\n' << unlock;
}

int main() {
    cout << nounitbuf;  // note: turn off buffering
    vector<future<void>> tasks;
    for (int i{}; i != 10; ++i) {
        tasks.push_back(async(launch::async, &print, i + 1));
    }
}

This corrected program produced the following output for a sample run; note that while the relative order is not guaranteed, there is no “messy” interleaved output:

This is line 1
This is line 2
This is line 4
This is line 3
This is line 6
This is line 5
This is line 8
This is line 7
This is line 10
This is line 9

These manipulators can also be used with std::cerr and std::clog to prevent messy output on the console when combining output from more than one stream, however it may be advisable both to turn off buffering for std::cout (with a call to std::cout.setf(std::ios::unitbuf);) and to “untie” it from std::cerr (with std::cerr.tie(nullptr);).

There are other ways of solving the same problem, usually using a std::lock_guard<std::mutex>, but this requires creating a sub-scope for every single std::cout etc. call in your program. (The need for a global mutex is also still present.) I feel the implementation of the lock/unlock functionality inside custom manipulators is neat, and solves the problem efficiently and in good C++ style.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s