【问题标题】:Synchronized call of functions using std::condition_variable使用 std::condition_variable 同步调用函数
【发布时间】:2020-04-07 18:14:57
【问题描述】:

又沉迷于学习并发,尝试解决this problem

简而言之,我有一个类和 3 个函数。我需要同步他们的通话(需要打印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

这怎么可能?这段代码是不是容易出现死锁?我的意思是,当第一个线程在函数second 中获取互斥锁时,它不会永远等待,因为现在获取互斥锁后,我们无法更改变量mfirst

【问题讨论】:

  • cv.wait() unlocks the mutex 在等待时,允许另一个线程获取它
  • 感谢@RemyLebeau,现在我明白它不会导致死锁。但是它怎么能在所有这些之前调用第三呢? cv 不应该等待布尔值变为真吗?
  • 您的代码没有调用cv.notify_all()cv.notify_one() 来通知等待线程cv 已发出信号,以便它们可以停止等待并重新获取互斥锁以检查其谓词条件。致电mfirst = true;msecond = true; 后,您需要通知cv。在没有cv.notify_...() 的情况下调用cv.wait() 完全违背了使用conditional_variable 的全部目的
  • 仍然,我无法理解线程是如何通过这一行的:cv.wait(l, [this] {return (mfirst && msecond) == true; });
  • 阅读我在之前评论中链接到的文档。

标签: c++ concurrency synchronization mutex deadlock


【解决方案1】:

你有一个非常微妙的错误。

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

这条线没有做你所期望的。它仅使用一个参数printThird 初始化一个线程,而不是使用您指定的 3 个参数进行初始化。那是因为您不小心使用了comma expression。结果,&amp;Foo::thirdt 就被丢弃了,甚至永远不会调用成员函数。

在您的代码中调用的唯一函数是 printFirstprintSecondprintThird。如果没有任何同步,它们可能会以任意顺序打印。你甚至可能得到交错输出。

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

这里是更正的版本。花括号是为了避免编译器将其视为函数声明,因为most vexing parse

附带说明,如 cmets 中所述,您使用的条件变量是错误的。

【讨论】:

  • 去掉多余的括号。我会更新我的答案
  • 实际上,在编译器将其解释为函数声明时,放置两个括号是有原因的。
  • 对。然后你可以使用花括号或复制初始化。
  • 是的,我已经这样做了,虽然我不太清楚为什么前两个参数被丢弃。谢谢
  • 因为逗号操作符。基本上,如果你写int a = (++x, y);,那么++x会被评估并丢弃,a等于y。你可以在这里阅读更多内容en.cppreference.com/w/cpp/language/operator_other
猜你喜欢
  • 2014-01-25
  • 1970-01-01
  • 1970-01-01
  • 2012-11-02
  • 1970-01-01
  • 2019-01-22
  • 2011-04-15
  • 2012-07-25
相关资源
最近更新 更多