3

This is my first attempt at writing multithreaded C++ code and it seems to have created a data race. Here is the complete file. It was compiled as: g++ -pthread foo.cpp

#include <iostream>
#include <iomanip>
#include <thread>
const int SIZE = 5;

void mult(int x, int y) {
    std::cout.width(3); 
    std::cout << std::right << x * y << "* ";
}

void add(int x, int y) {
    std::cout.width(3); 
    std::cout << std::right << x + y << "+ ";
}

int main() {
    int a = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            std::thread first(mult, i, j);
            std::thread second(add, i, j);
            first.join();
            second.join();
            std::cout << " | ";
        }
        std::cout << "\n";
    }
     return 0;
}

The output is scrambled in a non-reproducible manner on each run, for example:

  0*   0+  |   0*   1+  |   2  0+ *  |   0*   3+  |   0*   4+  | 
  0*   1+  |   1*   2+  |   2*   3+  |   3*   4+  |   4*   5+  | 
  0*   2+  |   2*   3+  |   4*   4+  |   6*   5+  |   8*   6+  | 
  0*   3+  |   3  4* +  |   6*   5+  |   9*   6+  |  12*   7+  | 
  0*   4+  |   4*   5+  |   8*   6+  |  12*   7+  |  16*   8+  | 

or

  0*   0+  |   0*   1+  |   0*   2+  |   0*   3+  |   0*   4+  | 
  0*   1+  |   1*   2+  |   2*   3+  |   3*   4+  |   4*   5+  | 
  0*   2+  |   2*   3+  |   4*   4+  |   6*   5+  |   8*   6+  | 
  0*   3+  |   3*   4+  |   6*   5+  |   9* 6+  |  12*   7+  | 
  0*   4+  |   4*   5+  |   8*   6+  |  12*   7+  |  16*   8+  | 

Is there any way around this problem? I've learned a lot about cout objects from this, but is it the rule that only one thread should be allowed to access cout at a time, especially when using iomanip?

Edit: I understand that as per: http://www.cplusplus.com/reference/iomanip/setw/ That using iomanip in this fashion may cause data races. So the question is, should this just not be attempted? Should each thread to cout be created, do its business, then joined? (i.e. no threading at all) and that's that? If so, that's fine, the main idea with concurrency would be more about having a program open multiple concurrent fstream objects, so that the user would not have to wait on that, and one thread to cout would be fine. What I'm asking is, is that the standard approach?

neutrino_logic
  • 1,289
  • 1
  • 6
  • 11
  • The answer to correct multi-threaded non-interleaving output is very complicated. I know there is a great video on YouTube from Herb Sutter which deals with this. – engf-010 Sep 21 '19 at 23:27
  • 1
    Possible duplicate of [Why is my program printing garbage?](https://stackoverflow.com/questions/34710027/why-is-my-program-printing-garbage) – walnut Sep 21 '19 at 23:35
  • Do you mind whether the multiplication or division is printed first in each section? If you do, there's no sense in having the IO in separate threads at all, have the threads calculate results and afterwards print them in the desired order. – JMAA Sep 21 '19 at 23:49
  • As for the interleaving, I'd recommend having a separate function that contains all of the `iostream` and `iomanip` functionality, protected by a `std::mutex` via a `std::lock_guard` – JMAA Sep 21 '19 at 23:50

2 Answers2

2

You could use a std::mutex and a std::lock_guard:

#include <iomanip>
#include <iostream>
#include <mutex>
#include <thread>
const int SIZE = 5;

std::mutex iomutex;

void mult(int x, int y) {
    // Complex, time-consuming calculations run multithreaded
    auto res = x * y;
    // lock stops other threads at this point
    std::lock_guard<std::mutex> lock(iomutex);
    // IO is singlethreaded
    std::cout.width(3); 
    std::cout << std::right << res << "* ";
    // lock leaves scope and is unlocked, next thread can start IO
}

void add(int x, int y) {
    // Complex, time-consuming calculations run multithreaded
    auto res = x + y;
    // lock stops other threads at this point
    std::lock_guard<std::mutex> lock(iomutex);
    // IO is singlethreaded
    std::cout.width(3); 
    std::cout << std::right << res << "+ ";
    // lock leaves scope and is unlocked, next thread can start IO
}

int main() {
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            std::thread first(mult, i, j);
            std::thread second(add, i, j);
            first.join();
            second.join();
            std::cout << " | ";
        }
        std::cout << "\n";
    }
     return 0;
}

In this example multithreading makes no sense but in bigger examples you would only guard input/output. Calculations run in parallel.

Thomas Sablik
  • 16,127
  • 7
  • 34
  • 62
  • Doesn't guard against the second thread locking the mutex before the first. Not likely to happen, but still entirely possible. – Graeme Sep 21 '19 at 23:42
  • @Graeme As far as I understand the question the order of the operations and output doesn't matter. The only thing that matters in this question is that one thread doesn't start output while the other thread is outputting. – Thomas Sablik Sep 21 '19 at 23:50
  • Thanks, my head has exploded, and I have more work to do obviously. What I'm actually worried about is using threads with fstream where they seem more useful than with cout. Cout is a one-time program-lifetime static object, apparently, and you can make multiple fstream objects, so perhaps that will not be a problem. – neutrino_logic Sep 22 '19 at 00:17
1

In this case it is probably best just to do all your output from the main thread:

#include <iostream>
#include <iomanip>
#include <thread>
const int SIZE = 5;

void mult(int &res, int x, int y) {
    res = x * y;
}

void add(int &res, int x, int y) {
    res = x + y;
}

int main() {
    int a = 0;
    for (int i = 0; i < SIZE; i++) {
        for (int j = 0; j < SIZE; j++) {
            int mult_res, add_res;
            std::thread first(mult, std::ref(mult_res), i, j);
            std::thread second(add, std::ref(add_res), i, j);
            first.join();
            second.join();
            std::cout.width(3);
            std::cout << std::right << mult_res << "* ";
            std::cout.width(3);
            std::cout << std::right << add_res << "+ | " ;
        }
        std::cout << "\n";
    }
    return 0;
}
Graeme
  • 2,971
  • 21
  • 26
  • That's sort of like having the first.join come right after the std::thread first isn't it? Then there is no problem, everything prints fine, but no need for either. So, in most cases, one thread to cout is all you will need? – neutrino_logic Sep 22 '19 at 00:10
  • @neutrino_logic, I am assuming that in a real example the calculations will be the most complex part and the advantage comes from parallelizing them. If you wanted you could also format the result inside the thread and return strings to output instead of ints. – Graeme Sep 22 '19 at 00:16
  • @neutrino_logic At the end of the day, it comes down to what you can tolerate in terms of variation in the table. This orders everything. Another approach may be to create threads to output different each line, then lock a mutex when outputting the line. This way the lines might be out of order, but the rest is fine. – Graeme Sep 22 '19 at 00:22
  • Oh, that makes sense... although my admittedly limited understanding is that threads are run concurrently on one processor in this implementation, that true parallelization comes from running those threads through a GPU or something like that? – neutrino_logic Sep 22 '19 at 00:23
  • @neutrino_logic, True parallelization can happen on one CPU with multiple cores, no GPU needed. This will happen in here if there is a multi-core CPU. It will also happen if it is running on a multi processor system with an appropriate os. – Graeme Sep 22 '19 at 00:30
  • @neutrino_logic If you are creating threads to speed up computations, you don't want to create more threads than the number of cores available. If the CPU uses hyper-threading, usually the os will report more than there is. Hyper-threading won't give you any advantages here, but it shouldn't hurt if you use up to the number of reported cores either. – Graeme Sep 22 '19 at 00:37