I’m working through an example from C++20 The Complete Guide by Nicolai M. Josuttis and have some questions about the code design and thread safety:
Closure by Value for tags: The example uses a closure that captures tags by value in threads that only read from it, not modify. What’s the advantage of capturing by value here instead of by reference?
Thread Safety of std::cout.put().flush(): Is the combination of std::cout.put().flush() thread-safe in C++20, considering it’s used inside a loop in a multithreaded context?
Lack of Synchronization: Given that std::cout.put().flush() is used without explicit synchronization mechanisms, are there any built-in guarantees for thread safety in this context?
#include <iostream>
#include <array>
#include <thread>
#include <latch>
using namespace std::literals; // for duration literals
void loopOver(char c) {
// loop over printing the char c:
for (int j = 0; j < c/2; ++j) {
std::cout.put(c).flush();
std::this_thread::sleep_for(100ms);
}
}
int main()
{
std::array tags{'.', '?', '8', '+', '-'}; // tags we have to perform a task for
// initialize latch to react when all tasks are done:
std::latch allDone{tags.size()}; // initialize countdown with number of tasks
// start two threads dealing with every second tag:
std::jthread t1{[tags, &allDone] {
for (unsigned i = 0; i < tags.size(); i += 2) { // even indexes
loopOver(tags[i]);
// signal that the task is done:
allDone.count_down(); // atomically decrement counter of latch
}
//...
}};
std::jthread t2{[tags, &allDone] {
for (unsigned i = 1; i < tags.size(); i += 2) { // odd indexes
loopOver(tags[i]);
// signal that the task is done:
allDone.count_down(); // atomically decrement counter of latch
}
//...
}};
//...
// wait until all tasks are done:
std::cout << "nwaiting until all tasks are donen";
allDone.wait(); // wait until counter of latch is zero
std::cout << "nall tasks donen"; // note: threads might still run
//...
}
3