neutrino_logic
neutrino_logic

Reputation: 1299

How to avoid data races when using std::cout and <iomanip> in multithreaded programs?

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?

Upvotes: 3

Views: 913

Answers (2)

Graeme
Graeme

Reputation: 3041

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;
}

Upvotes: 1

Thomas Sablik
Thomas Sablik

Reputation: 16448

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.

Upvotes: 2

Related Questions