'c++ std::atomic concurrency printing

Hello everyone I have a code for threads to print their number and number that they are counting:

void print_num_n_times(std::uintmax_t thread_num, std::uintmax_t num_times_to_print = 100)
{
    static std::atomic_bool is_printing{ false };
    std::uintmax_t i = 0;
    while (i < num_times_to_print)
    {
        //if the thread is not acquired.
        while (is_printing.load());
        //if the thread previous value was false (maybe another thread was intterupting.
        if (!is_printing.exchange(true)) {
            std::cout << "Output from thread " << thread_num << " Value: " << i + 1 << '\n';
            is_printing.exchange(false);
            i++;
        }
    }
}

and this is the only solution I have found for multiple threads to print nicely like this (2 threads count to 5):

Output from thread 2 Value: 1
Output from thread 2 Value: 2
Output from thread 2 Value: 3
Output from thread 2 Value: 4
Output from thread 2 Value: 5
Output from thread 1 Value: 1
Output from thread 1 Value: 2
Output from thread 1 Value: 3
Output from thread 1 Value: 4
Output from thread 1 Value: 5

Is there a better solution, because it's slow and I feel like there could be a better solution. Any help would be appreciated!



Solution 1:[1]

Here's a simplistic version of your code using a std::mutex and std::lock_guards to demonstrate one-shot threads with no other synchronization than using said mutex. In real situations you'd often use a std::condition_variable and a std::unique_lock to signal longer running threads that it's time to do some work.

#include <cstdint>
#include <iostream>
#include <mutex>
#include <thread>
#include <vector>

void print_num_n_times(std::mutex& mtx, std::uintmax_t thread_num,
                       std::uintmax_t num_times_to_print)
{
    std::lock_guard<std::mutex> lock(mtx); // wait here until acquiring the mutex lock

    // this section will only be run by one thread (having locked the mutex) at a time
    for (decltype(num_times_to_print) i = 0; i < num_times_to_print; ++i) {
        std::cout << "Output from thread " << thread_num << " Value: " << i + 1 << '\n';
    }
} // when `lock` goes out of scope, the mutex lock is released

// An auto joining thread for C++11, in C++20, use std::jthread
struct jthread : std::thread {
    using std::thread::operator=;
    ~jthread() { if(joinable()) join(); }
};

int main() {
    std::mutex mtx;                   // a mutex to use by all threads
    std::vector<jthread> threads(3);  // create 3 jthreads to run `print_num_n_times`

    std::lock_guard<std::mutex> lock(mtx); // lock while starting all threads

    // start the threads (while locking the mutex)
    for (size_t i = 0; i < threads.size(); ++i) {
        threads[i] = std::thread(print_num_n_times, std::ref(mtx), i + 1, 5);
    }
} // 1. the lock_guard goes out of scope - the mutex is released
  // 2. threads start acquiring the mutex lock (in an indeterminate order) 
  //    and
  // 2. the vector goes out of scope and its destructor is called
  // 3. the jthreads in the vector all join()s their threads in their destructors
  // 4. finally the mutex is destroyed

Possible output:

Output from thread 3 Value: 1
Output from thread 3 Value: 2
Output from thread 3 Value: 3
Output from thread 3 Value: 4
Output from thread 3 Value: 5
Output from thread 1 Value: 1
Output from thread 1 Value: 2
Output from thread 1 Value: 3
Output from thread 1 Value: 4
Output from thread 1 Value: 5
Output from thread 2 Value: 1
Output from thread 2 Value: 2
Output from thread 2 Value: 3
Output from thread 2 Value: 4
Output from thread 2 Value: 5

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ted Lyngmo