Бесконечное ожидание по условной переменной - PullRequest
0 голосов
/ 26 сентября 2019

Упрощенная цель состоит в том, чтобы принудительно вызывать 3 функции-члена в 3 разных потоках один за другим (поток A вызывает F :: first, поток BF :: second, поток CF :: third).

InЧтобы получить порядок выполнения потоков, я использовал 1 условную переменную и 2 bools, указывающие, закончили ли первый и второй потоки свою работу.

В коде:

std::mutex mtx;
std::condition_variable cv;
bool firstPrinted = false;
bool secondPrinted = false;

class F {
public:    
    void first(std::function<void()> printFirst) {
        std::unique_lock<std::mutex> lck(mtx);
        std::cout << "first\n";
        printFirst();
        firstPrinted = true;
        cv.notify_one();
    }

    void second(std::function<void()> printSecond) {
        std::unique_lock<std::mutex> lck(mtx);
        std::cout << "second\n";
        cv.wait(lck, []() { return firstPrinted; });
        printSecond();
        secondPrinted = true;
        cv.notify_one();
    }

    void third(std::function<void()> printThird) {
        std::unique_lock<std::mutex> lck(mtx);
        std::cout << "third\n";
        cv.wait(lck, []() { return secondPrinted; });
        printThird();
    }
};

auto first = []() {
    std::cout << "1";
};
auto second = []() {
    std::cout << "2";
};
auto third = []() {
    std::cout << "3";
};
F f;
std::thread A(&F::first,  &f, first);
std::thread B(&F::second, &f, second);
std::thread C(&F::third,  &f, third);
A.join(); B.join(); C.join();

Теперь давайте рассмотримэта ситуация:

Поток A не запускается первым - независимо от того, был ли первый начальный поток B или C, они оба блокируют (ждут), пока не получат уведомление (B блокирует до получения уведомления от A, и C блокирует до получения уведомления от B)

Бесконечное ожидание (или, возможно, тупик!?) Появляется, когда первым начальным потоком является C, что всегда приводит к следующему выводу:

third
second
first
...and stalling here

Теоретически это не должно происходить, потому что вызов cv.ожидание в потоке C разблокирует мьютекс, который позволяет запускать поток B, который, в свою очередь, также ожидает (потому что условие не стало истинным), и поэтому он разблокирует заблокированный мьютекс как wчто позволяет сначала запустить поток A, который, наконец, должен войти в критическую секцию и уведомить B.

  1. Какой путь вызова вызывает остановку программы?

  2. Какой нюанс я упустил?

Пожалуйста, поправьте меня, если я ошибся в приведенных выше мыслях.

1 Ответ

4 голосов
/ 26 сентября 2019

std::condition_variable::notify_one() активирует один потоков, ожидающих condition_variable.Если несколько потоков ожидают, один будет выбран.Он проснется, снова получит проверку блокировки, это предикат.Если этот предикат все еще false, он вернется в состояние ожидания и уведомление по существу потеряно.

Это то, что происходит здесь, когда поток, выполняющий first, выполняется последним.Когда он достигнет notify_one, два потока будут ждать condition_variable.Если он уведомляет поток, выполняющий third, его предикат все равно вернет false.Этот поток проснется, провалит проверку предикатов и вернется к ожиданию.Ваш процесс теперь не имеет запущенных потоков и заморожен.

Решение заключается в использовании std::condition_variable::notify_all().Эта функция пробуждает всех ожидающих потоков, которые по одному блокируют mutex и проверяют свой собственный предикат.

...