Как избежать гонок данных при использовании std :: cout и <iomanip>в многопоточных программах? - PullRequest
3 голосов
/ 22 сентября 2019

Это моя первая попытка написания многопоточного кода на C ++, и похоже, что он создал гонку данных.Вот полный файл.Он был скомпилирован как: 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;
}

Вывод скремблируется невоспроизводимым образом при каждом запуске, например:

  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+  | 

или

  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+  | 

Есть ли способ обойти эту проблему?Из этого я многое узнал об объектах cout, но разве это правило, что только одному потоку должен быть разрешен доступ к cout одновременно, особенно при использовании iomanip?

Редактировать: я понимаю, что согласно:http://www.cplusplus.com/reference/iomanip/setw/ То, что использование iomanip таким способом может вызвать скачки данных.Итак, вопрос в том, стоит ли пытаться это делать?Должен ли каждый поток создаваться, заниматься своим делом, а затем присоединяться?(т. е. вообще нет потоков) и все?Если это так, это нормально, основная идея с параллелизмом будет заключаться в том, чтобы программа открывала несколько параллельных объектов fstream, так что пользователю не пришлось бы ждать этого, и один поток для cout был бы в порядке.То, что я спрашиваю, это стандартный подход?

Ответы [ 2 ]

2 голосов
/ 22 сентября 2019

Вы можете использовать std :: mutex и 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;
}

В этом примере многопоточность не имеет смысла, но в более крупных примерахвы бы только охраняли ввод / вывод.Расчеты выполняются параллельно.

1 голос
/ 22 сентября 2019

В этом случае, вероятно, лучше всего сделать весь вывод из основного потока:

#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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...