gpt4 book ai didi

multithreading - 等待分离线程完成的正确方法是什么?

转载 作者:行者123 更新时间:2023-12-02 06:00:32 27 4
gpt4 key购买 nike

看这个示例代码:

void OutputElement(int e, int delay)
{
this_thread::sleep_for(chrono::milliseconds(100 * delay));
cout << e << '\n';
}

void SleepSort(int v[], uint n)
{
for (uint i = 0 ; i < n ; ++i)
{
thread t(OutputElement, v[i], v[i]);
t.detach();
}
}

它开始 n新线程,每个线程在输出值和完成之前都会休眠一段时间。在这种情况下等待所有线程完成的正确/最佳/推荐方式是什么?我知道如何解决这个问题,但我想知道在这种情况下我应该使用什么推荐的多线程工具/设计(例如 condition_variablemutex 等...)?

最佳答案

现在是略有不同的答案。我确实有点意思,因为我大多同意另一个答案和“不要分离,而是加入”的评论。

首先假设没有join() .并且您必须使用 mutex 在线程之间进行通信和 condition_variable .这真的不难也不复杂。它允许任意丰富的通信,可以是任何你想要的,只要它只在互斥锁被锁定时进行通信。

现在,这种交流的一个非常常见的习语就是说“我完成了”的状态。子线程将设置它,父线程将等待 condition_variable,直到子线程说“我完成了”。这个习惯用法实际上很常见,值得一个方便的函数来封装mutex。 , condition_variable和状态。
join()正是这种便利功能。

但恕我直言,必须小心。当有人说:“从不 detach ,总是 join ”时,可以解释为:永远不要让你的线程通信比“我完成了”更复杂。

对于父线程和子线程之间更复杂的交互,考虑一个父线程启动多个子线程出去,独立寻找问题的解决方案的情况。当任何线程第一次发现问题时,就会将其传达给父​​级,然后父级可以采用该解决方案,并告诉所有其他线程他们不再需要搜索。

例如:

#include <chrono>
#include <iostream>
#include <iterator>
#include <random>
#include <thread>
#include <vector>

void OneSearch(int id, std::shared_ptr<std::mutex> mut,
std::shared_ptr<std::condition_variable> cv,
int& state, int& solution)
{
std::random_device seed;
// std::mt19937_64 eng{seed()};
std::mt19937_64 eng{static_cast<unsigned>(id)};
std::uniform_int_distribution<> dist(0, 100000000);
int test = 0;
while (true)
{
for (int i = 0; i < 100000000; ++i)
{
++test;
if (dist(eng) == 999)
{
std::unique_lock<std::mutex> lk(*mut);
if (state == -1)
{
state = id;
solution = test;
cv->notify_one();
}
return;
}
}
std::unique_lock<std::mutex> lk(*mut);
if (state != -1)
return;
}
}

auto findSolution(int n)
{
std::vector<std::thread> threads;
auto mut = std::make_shared<std::mutex>();
auto cv = std::make_shared<std::condition_variable>();
int state = -1;
int solution = -1;
std::unique_lock<std::mutex> lk(*mut);
for (uint i = 0 ; i < n ; ++i)
threads.push_back(std::thread(OneSearch, i, mut, cv,
std::ref(state), std::ref(solution)));
while (state == -1)
cv->wait(lk);
lk.unlock();
for (auto& t : threads)
t.join();
return std::make_pair(state, solution);
}

int
main()
{
auto p = findSolution(5);
std::cout << '{' << p.first << ", " << p.second << "}\n";
}

上面我创建了一个“虚拟问题”,其中一个线程搜索它需要查询多少次 URNG,直到找到数字 999。父线程放置 5 个子线程来处理它。子线程工作了一段时间,然后每隔一段时间,查看一下是否有其他线程找到了解决方案。如果是这样,他们就辞职,否则他们继续工作。主线程等待直到找到解决方案,然后 加入与所有子线程。

对我来说,使用 bash 时间工具,这会输出:
$ time a.out
{3, 30235588}

real 0m4.884s
user 0m16.792s
sys 0m0.017s

但是,如果不是加入所有线程,而是分离那些尚未找到解决方案的线程怎么办。这可能看起来像:
    for (unsigned i = 0; i < n; ++i)
{
if (i == state)
threads[i].join();
else
threads[i].detach();
}

(代替上面的 t.join() 循环)。对我来说,现在运行时间为 1.8 秒,而不是上面的 4.9 秒。 IE。子线程不会经常相互检查,因此 main 只是分离工作线程并让操作系统将它们关闭。这对于这个例子是安全的,因为子线程拥有它们所接触的一切。没有任何东西会从它们下面被破坏。

通过注意到即使找到解决方案的线程也不需要加入,可以实现最后一次迭代。所有线程都可以分离。代码实际上要简单得多:
auto findSolution(int n)
{
auto mut = std::make_shared<std::mutex>();
auto cv = std::make_shared<std::condition_variable>();
int state = -1;
int solution = -1;
std::unique_lock<std::mutex> lk(*mut);
for (uint i = 0 ; i < n ; ++i)
std::thread(OneSearch, i, mut, cv,
std::ref(state), std::ref(solution)).detach();
while (state == -1)
cv->wait(lk);
return std::make_pair(state, solution);
}

并且性能保持在1.8秒左右。

这里仍然(有点)与解决方案查找线程有效连接。但它是通过 condition_variable::wait 完成的。而不是 join .
thread::join()对于您的父/子线程通信协议(protocol)只是“我完成了”的非常常见的习语来说,这是一个便利功能。首选 thread::join()在这种常见情况下,因为它更容易阅读,也更容易编写。

但是,不要不必要地将自己限制在这样一个简单的父/子通信协议(protocol)上。当手头的任务需要时,不要害怕构建自己的更丰富的协议(protocol)。在这种情况下, thread::detach()通常会更有意义。 thread::detach()不一定意味着一劳永逸的线程。它可以简单地表示您的通信协议(protocol)比“我完成了”更复杂。

关于multithreading - 等待分离线程完成的正确方法是什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/26444647/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com