Является ли следующий код хорошим решением для многопоточного счетчика приращений и печати? - PullRequest
1 голос
/ 22 февраля 2020

Можете ли вы высказать свое мнение, пожалуйста? Что бы вы сделали по-другому? Я имею в виду, как вы думаете, было бы лучше, если бы я сделал это с помощью std :: task или std :: mutex, std :: condition_variable, et c? Я переусердствовал, чтобы контролировать потоки с 2 флагами?

std::atomic<int> counter = { 0 };
std::atomic<bool> switchFlag = { false };
std::atomic<bool> finished = { false };
constexpr int MAX_NUM = 10;

void increment(){
    while (!finished.load()){
        if (!switchFlag.load()){
            std::cout << "incremented to =" << ++counter << '\n';
            switchFlag.store(true);
        }
    }
}

void print(){
    while (!finished.load()) {
        if (switchFlag.load()){
            std::cout << "counter=" << counter.load() << '\n';
            if (counter.load() >= MAX_NUM)
                finished.store(true);

            switchFlag.store(false);
        }
    }
}

int main() {
    auto t1 = std::thread(increment);
    auto t2 = std::thread(print);
    t1.join();
    t2.join();
    return 0;
}

1 Ответ

5 голосов
/ 22 февраля 2020

Это, прямо скажем, невероятно ужасно на типичном, реалистичном c оборудовании. Наиболее очевидная проблема заключается в следующем:

Посмотрите на поток в increment. До запуска print значение if будет false, а значение while будет истинным. Прогнозирование ветвления начнет становиться твердо убежденным, что if будет false.

Затем, когда поток print устанавливает switchFlag в false и вам нужно increment для выполнения как настолько быстро, насколько это возможно, потому что другой поток будет ожидать его, вы выбираете наихудшую предсказуемую ветвь, которую только можно себе представить.

Итак, в тот самый момент, когда это наиболее критично, вы выполняете как можно быстрее, вы столкнуться с наихудшей производительностью, которую процессор может дать вам, взорвав все конвейеры с непредсказуемой ветвью.

Я настоятельно призываю вас не пытаться составлять сложные операции из примитивных операций, таких как загрузка атома c и магазины. Для этого требуется глубокая экспертиза платформы. Используйте высокоуровневые функции (например, мьютексы и условные переменные) для высокоуровневых операций (например, ожидания).

Но никогда не бывает хорошего способа реализовать два потока, которым необходимо чередовать выполнение. В любом случае, когда два потока не могут одновременно продвигаться вперед, нет веской причины иметь два потока.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...