我想正确地同步不同的线程,但是到目前为止,我只能编写一个优雅的解决方案。有人可以指出我如何改进以下代码吗?
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
执行。该函数接受两个长时间运行的函数func1
和func2
以及两个int引用作为参数。 所有线程退出func2
后,线程应仅调用func1
。参数has_finished
用于协调不同的线程:输入函数后,has_arguments
为零。然后每个std::thread
递减值并调用长时间运行的函数func1
。离开func1
之后,has_finished
再次增加。只要此值不为原始值零,线程就会等待。然后,每个线程都在func2
上工作。主要功能显示在末尾。
如何更好地协调两个线程?我当时在考虑使用std::mutex
和std::condition_variable
,但不知道如何正确使用它们?有人知道我如何改进该程序吗?
答案 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变量递减。