如何正确同步这两个线程?

时间:2019-09-25 16:24:16

标签: c++ multithreading performance mutex

我想正确地同步不同的线程,但是到目前为止,我只能编写一个优雅的解决方案。有人可以指出我如何改进以下代码吗?

typedef void (*func)();

void thread(func func1, func func2, int& has_finished, int& id) {
    has_finished--;
    func1();
    has_finished++;
    while (has_finished != 0) std::cout << "thread " << id << " waiting\n";
    std::cout << "thread" << id << "resuming\n";
    func2();
}

int main() {
    int has_finished(0), id_one(0), id_two(1);
    std::thread t1(thread, fun, fun, std::ref(has_finished), std::ref(id_one));
    std::thread t2(thread, fun, fun, std::ref(has_finished), std::ref(id_two));
    t1.join();
    t2.join();
};

程序的要旨由功能thread描述。该函数由两个std::thread执行。该函数接受两个长时间运行的函数func1func2以及两个int引用作为参数。 所有线程退出func2后,线程应仅调用func1 。参数has_finished用于协调不同的线程:输入函数后,has_arguments为零。然后每个std::thread递减值并调用长时间运行的函数func1。离开func1之后,has_finished再次增加。只要此值不为原始值零,线程就会等待。然后,每个线程都在func2上工作。主要功能显示在末尾。

如何更好地协调两个线程?我当时在考虑使用std::mutexstd::condition_variable,但不知道如何正确使用它们?有人知道我如何改进该程序吗?

2 个答案:

答案 0 :(得分:3)

不要自己写这个。这种同步被称为“闩锁”(或更一般地说是“屏障”),可通过各种库和C ++ Concurrency TS获得(也可能以某种形式将其导入C ++ 20)。 / p>

例如,使用a version from Boost

#include <iostream>
#include <thread>

#include <boost/thread/latch.hpp>

void f(boost::latch& c) {
    std::cout << "Doing work in round 1\n";
    c.count_down_and_wait();
    std::cout << "Doing work in round 2\n";
}

int main() {
    boost::latch c(2);

    std::thread t1(f, std::ref(c)), t2(f, std::ref(c));
    t1.join();
    t2.join();
}

答案 1 :(得分:2)

由于竞争条件,您选择的方法将无法实际使用并导致不确定的行为。如您所料,您需要一个条件变量。

这里是一个Gate类,展示了如何使用条件变量来实现一个门,该门等待一定数量的线程到达该门,然后继续:

#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <sstream>
#include <utility>
#include <cassert>

struct Gate {
 public:
    explicit Gate(unsigned int count = 2) : count_(count) { }  // How many threads need to reach the gate before it unlocks
    Gate(Gate const &) = delete;
    void operator =(Gate const &) = delete;

    void wait_for_gate();

 private:
    int count_;
    ::std::mutex count_mutex_;
    ::std::condition_variable count_gate_;
};

void Gate::wait_for_gate()
{
    ::std::unique_lock<::std::mutex> guard(count_mutex_);
    assert(count > 0); // Count being 0 here indicates an irrecoverable programming error.
    --count_;
    count_gate_.wait(guard, [this](){ return this-> count_ <= 0; });
    guard.unlock();
    count_gate_.notify_all();
}

void f1()
{
    ::std::ostringstream msg;
    msg << "In f1 with thread " << ::std::this_thread::get_id() << '\n';
    ::std::cout << msg.str();
}

void f2()
{
    ::std::ostringstream msg;
    msg << "In f2 with thread " << ::std::this_thread::get_id() << '\n';
    ::std::cout << msg.str();
}

void thread_func(Gate &gate)
{
    f1();
    gate.wait_for_gate();
    f2();
}

int main()
{
    Gate gate;
    ::std::thread t1{thread_func, ::std::ref(gate)};
    ::std::thread t2{thread_func, ::std::ref(gate)};
    t1.join();
    t2.join();
}

希望此代码的结构看起来与您的代码足够相似,您可以了解这里发生的情况。通过阅读代码,您似乎正在寻找所有要执行func1,然后是func2的线程。您不希望func2在任何线程正在执行func1时运行。

这可以看作是所有线程在继续运行func2之前都等待到达“ finishfunc1”位置的大门。

我在自己的本地版本的编译器资源管理器中测试了此代码。

另一个答案中的闩锁的主要缺点是它不是标准的C ++。我的Gate类是另一个答案中提到的闩锁类的简单实现,它是标准的C ++。

条件变量的基本工作方式是解锁互斥锁,等待通知,然后锁定该互斥锁并测试条件。如果条件为真,它将继续而不解锁互斥锁。如果条件为假,它将重新开始。

因此,在条件变量说条件为真之后,您必须做任何您需要做的事,然后解锁互斥锁并通知所有人您已经完成了。

此处的互斥锁保护共享计数变量。每当有共享值时,都应使用互斥量保护它,以使任何线程都无法看到处于不一致状态的该值。条件是线程可以等待该计数达到0,这表示所有线程都已将count变量递减。