Reputation: 493
If two different threads each try writing one piece of data each to a output stream, is it guaranteed that this operation is thread-safe for std::string_stream
, std::cout
, std::err
, and std::fstream
(in C++11 onward)? (In other words, are there any guarantees that writing to the same stream from multiple threads won't break the stream or interleave single pieces of data?)
By "one piece of data", I mean "with one call to the pipe operator <<
using a standard library overload". For example, if one thread writes a string containing a million 0
s in a row, and the other thread writes a string containing a million 1
s in a row, are there any guarantees that there won't be any 0
s mixed with 1
s?
Code testing this idea: This code outputs a bunch of 1
s on one thread, and a bunch of 0
s in another thread. I haven't seen any interlacing when piping the output to a file.
I tested this code out in Ubuntu 18.04, using gcc-7.3.0
and compiling with the flags -std=c++17 -pthread -O3 main.cc -o main
, and I didn't observe any mixing of 1
s and 0
s.
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
#include <chrono>
auto now() {
return std::chrono::high_resolution_clock::now();
}
typedef decltype(now()) now_time_t;
int main(int argc, char** argv) {
std::stringstream A {}, B {};
int count = argc > 1 ? std::stoi(argv[1]) : 1000;
for(int i = 0; i < count; ++i) {
A << "0";
B << "1";
}
volatile bool waiting = true;
now_time_t t1_start_time, t2_start_time, t1_end_time, t2_end_time;
std::thread t1 = std::thread([&] {
while(waiting) ;
t1_start_time = now();
std::cout << A.rdbuf();
t1_end_time = now();
});
std::thread t2 = std::thread([&] {
while(waiting) ;
t2_start_time = now();
std::cout << B.rdbuf();
t2_end_time = now();
});
waiting = false;
t1.join();
t2.join();
auto t1_total_time = (t1_end_time - t1_start_time).count();
auto t2_total_time = (t2_end_time - t2_start_time).count();
std::cerr << "Time difference: " << (t1_start_time - t2_start_time).count() << std::endl;
std::cerr << "t1 total time: " << t1_total_time << std::endl;
std::cerr << "t2 total time: " << t2_total_time << std::endl;
}
Upvotes: 1
Views: 1045
Reputation: 52471
[iostreams.threadsafety]/1 Concurrent access to a stream object (30.8, 30.9), stream buffer object (30.6), or C Library stream (30.11) by multiple threads may result in a data race (4.7) unless otherwise specified (30.4). [ Note: Data races result in undefined behavior (4.7). —end note ]
[iostream.objects.overview]/5 Concurrent access to a synchronized (30.5.3.4) standard iostream object’s formatted and unformatted input (30.7.4.1) and output (30.7.5.1) functions or a standard C stream by multiple threads shall not result in a data race (4.7). [ Note: Users must still synchronize concurrent use of these objects and streams by multiple threads if they wish to avoid interleaved characters. —end note ]
Here "standard iostream object" is one of std::cin
, std::cout
, std::cerr
, std::clog
and the corresponding wide streams (wcin
et al). Such a stream is synchronized unless sync_with_stdio(false)
has been previously called on it.
Thus, concurrent writes to std::cout
et al don't result in a data race, but are allowed to interleave characters. Concurrent writes to any other stream (e.g. string stream, file stream) exhibit undefined behavior by way of a data race.
Upvotes: 2