What happens when you write without synchronization to
std::cout? You get a mess. With C++20, this should not be anymore.
Before I present synchronized output streams with C++20, I want to show non-synchronized output in C++11.
The boss has six workers (lines 1 – 2). Each worker has to take care of three work packages that take 1/5 second each (line 3). After the worker is done with his work package, he screams out loudly to the boss (line 4). Once the boss receives notifications from all workers, he sends them home (line 5).
What a mess for such a simple workflow! Each worker screams out his message ignoring his coworkers!
std::coutis thread-safe: The C++11 standard guarantees that you need not protect
std::cout. Each character is written atomically. More output statements like those in the example may interleave. This interleaving is only a visual issue; the program is well-defined. This remark is valid for all global stream objects. Insertion to and extraction from global stream objects (
std::cout, std::cin, std::cerr, and
std::clog) is thread-safe. To put it more formally: writing to
std::coutis not participating in a data race but does create a race condition. This means that the output depends on the interleaving of threads. Read more about the terms data race and race condition in my previous post: Race Conditions versus Data Races.
How can we solve this issue? With C++11, the answer is straightforward: use a lock such as
std::lock_guard to synchronize the access to
std::cout. For more information about locks in C++11, please read my previous post Prefer Locks to Mutexes.
coutMutex in line (1) protects the shared object
std::cout. Putting the
coutMutex into a
std::lock_guard guarantees that the
coutMutex is locked in the constructor (line 2) and unlocked in the destructor (line 3) of the
std::lock_guard. Thanks to the
coutMutex guarded by the
coutLock the mess becomes harmony.
With C++20, writing synchronized to
std::cout is a piece of cake.
std::basic_syncbuf is a wrapper for a
std::basic_streambuf. It accumulates output in its buffer. The wrapper sets its content to the wrapped buffer when it is destructed. Consequently, the content appears as a contiguous sequence of characters, and no characters can interleave.
std::basic_osyncstream, you can directly write synchronously to
std::cout by using a named synchronized output stream
Here is how the previous program
coutUnsynchronized.cpp is refactored to write synchronized to
std::cout. So far, only GCC 11 supports synchronized output streams.
The only change to the previous program
coutUnsynchronized.cpp is that
std::cout is wrapped in a
std::osyncstream (line 1). When the
std::osyncstream goes out of scope in line (2), the characters are transferred and
std::cout is flushed. It is worth mentioning that the
std::cout calls in the main program do not introduce a data race and, therefore, need not be synchronized. The output happens before or after the output of the threads.
Because I use the
syncStream declared on line (3) only once, a temporary object may be more appropriate. The following code snippet presents the modified call operator:
std::basic_osyncstream syncStream offers two interesting member functions.
syncStream.emit()emits all buffered output and executes all pending flushes.
syncStream.get_wrapped()returns a pointer to the wrapped buffer.
cppreference.com shows how you can sequence the output of different output streams with the
get_wrapped member function.
But there is still one feature I want to give more insight into coroutines. In my next posts, I will start to play with the new keywords
Thanks a lot to my Patreon Supporters: Matt Braun, Roman Postanciuc, Tobias Zindl, G Prvulovic, Reinhold Dröge, Abernitzke, Frank Grimm, Sakib, Broeserl, António Pina, Sergey Agafyin, Андрей Бурмистров, Jake, GS, Lawton Shoemake, Jozo Leko, John Breland, Venkat Nandam, Jose Francisco, Douglas Tinkham, Kuchlong Kuchlong, Robert Blanch, Truels Wissneth, Kris Kafka, Mario Luoni, Friedrich Huber, lennonli, Pramod Tikare Muralidhara, Peter Ware, Daniel Hufschläger, Alessandro Pezzato, Bob Perry, Satish Vangipuram, Andi Ireland, Richard Ohnemus, Michael Dunsky, Leo Goodstadt, John Wiederhirn, Yacob Cohen-Arazi, Florian Tischler, Robin Furness, Michael Young, Holger Detering, Bernd Mühlhaus, Matthieu Bolt, Stephen Kelley, Kyle Dean, Tusar Palauri, Dmitry Farberov, Juan Dent, George Liao, Daniel Ceperley, Jon T Hess, Stephen Totten, Wolfgang Fütterer, Matthias Grün, Phillip Diekmann, Ben Atakora, Ann Shatoff, Rob North, and Bhavith C Achar.
Thanks, in particular, to Jon Hess, Lakshman, Christian Wittenhorst, Sherhy Pyton, Dendi Suhubdy, Sudhakar Belagurusamy, Richard Sargeant, Rusty Fleming, John Nebel, Mipko, Alicja Kaminska, Slavko Radman, and David Poole.
|My special thanks to Embarcadero|
|My special thanks to PVS-Studio|
|My special thanks to Tipi.build|
|My special thanks to Take Up Code|
I’m happy to give online seminars or face-to-face seminars worldwide. Please call me if you have any questions.
- Embedded Programmierung mit modernem C++ 12.12.2023 – 14.12.2023 (Präsenzschulung, Termingarantie)
Standard Seminars (English/German)
Here is a compilation of my standard seminars. These seminars are only meant to give you a first orientation.
- C++ – The Core Language
- C++ – The Standard Library
- C++ – Compact
- C++11 and C++14
- Concurrency with Modern C++
- Design Pattern and Architectural Pattern with C++
- Embedded Programming with Modern C++
- Generic Programming (Templates) with C++
- Clean Code with Modern C++
- Phone: +49 7472 917441
- Mobil:: +49 176 5506 5086
- Mail: schulung@ModernesCpp.de
- German Seminar Page: www.ModernesCpp.de
- Mentoring Page: www.ModernesCpp.org
Modernes C++ Mentoring,