Синхронизированный вызов функций с использованием std :: condition_variable - PullRequest
1 голос
/ 07 апреля 2020

Я снова увлекся изучением параллелизма и попытался решить эту проблему .

Короче, у меня есть класс и 3 функции. Мне нужно синхронизировать c их вызовы (необходимо напечатать FirstSecondThird).

Это станет более понятным с кодом ниже:

std::function<void()> printFirst = []() { std::cout << "First"; };
std::function<void()> printSecond = []() { std::cout << "Second"; };
std::function<void()> printThird = []() { std::cout << "Third"; };

class Foo {
    std::condition_variable cv;
    bool mfirst,msecond;
    std::mutex mtx;
public:
    Foo() {
        mfirst = false;
        msecond = false;
    }

    void first(std::function<void()> printFirst) {
        std::unique_lock<std::mutex> l(mtx);
        printFirst();
        mfirst = true;
    }

    void second(std::function<void()> printSecond) {
        std::unique_lock<std::mutex> l(mtx);
        cv.wait(l, [this]{return mfirst == true; });
        printSecond();
        msecond = true;
    }

    void third(std::function<void()> printThird) {
        std::unique_lock<std::mutex> l(mtx);
        cv.wait(l, [this] {return (mfirst && msecond) == true; });
        printThird();
    }
};

int main()
{
    Foo t;

    std::thread t3((&Foo::third, t, printThird));
    std::thread t2((&Foo::second, t, printSecond));
    std::thread t1((&Foo::first, t, printFirst));

    t3.join();
    t2.join();
    t1.join();

    return 0;
}

И угадайте, что мой вывод? Он печатает ThirdSecondFirst.

Как это возможно? Разве этот код не склонен к DEADLOCK? Я имею в виду, что когда первый поток получил мьютекс в функции second, не будет ли он ждать вечно, потому что теперь, когда мьютекс получен, мы не можем изменить переменную mfirst?

1 Ответ

3 голосов
/ 07 апреля 2020

У вас очень тонкий баг.

std::thread t3((&Foo::third, t, printThird));

Эта строка не делает то, что вы ожидаете. Он инициализирует поток только с одним аргументом printThird вместо инициализации с 3 указанными вами аргументами. Это потому, что вы случайно использовали выражение запятой . В результате, &Foo::third и t просто отбрасываются, а функции-члены никогда даже не вызываются.

Единственными функциями, которые вызываются в вашем коде, являются printFirst, printSecond и printThird. Без какой-либо синхронизации они могут печатать в произвольном порядке. Вы можете даже получить чередующийся вывод.

std::thread t3{&Foo::third, t, printThird};

Вот исправленная версия. Фигурные скобки предназначены для того, чтобы компилятор не воспринимал это как объявление функции из-за самого неприятного анализа

В качестве примечания, как упоминалось в комментариях, вы используете переменные условия неправильно.

...