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

How can I syncronize these two threads properly?

本文关键字:两个 线程 何正确 同步      更新时间:2023-10-16

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

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 << " waitingn";
std::cout << "thread" << id << "resumingn";
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 引用作为参数。线程应仅在所有线程退出func1后调用func2。参数has_finished用于协调不同的线程:输入函数后,has_arguments为零。然后,每个std::thread递减该值并调用长时间运行的函数func1。离开func1后,has_finished再次递增。只要此值不是其原始值零,线程就会等待。然后,每个线程都在func2.主函数显示在末尾。

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

不要自己写这个。这种同步称为"锁存器"(或更一般的"屏障"(,它可以通过各种库和C++并发 TS 获得。(它也可能以某种形式进入C++20。

例如,使用 Boost 中的版本:

#include <iostream>
#include <thread>
#include <boost/thread/latch.hpp>
void f(boost::latch& c) {
std::cout << "Doing work in round 1n";
c.count_down_and_wait();
std::cout << "Doing work in round 2n";
}
int main() {
boost::latch c(2);
std::thread t1(f, std::ref(c)), t2(f, std::ref(c));
t1.join();
t2.join();
}

由于争用条件,您选择的方法实际上不起作用,并导致未定义的行为。正如您所推测的,您需要一个条件变量。

下面是一个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时运行。

这可以被认为是一个门,所有线程都在等待到达"完成的func1"位置,然后再继续运行func2。

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

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

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

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

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