【问题标题】:Spawn multiple threads each returning multiple values产生多个线程,每个线程返回多个值
【发布时间】:2017-01-23 22:58:38
【问题描述】:

我正在尝试生成将执行给定任务的多个线程。此任务根据传入的内容而有所不同,并将返回多个值。

我尝试了以下几行但没有运气:

std::vector<std::tuple<std::thread, Task, Result1, Result2>> workers;
for (auto const& task : tasks) {
    Result1 result1;
    Result2 result2;
    std::tuple<std::thread, Task, Result1, Result2> worker = std::make_tuple(std::thread(&Slave::performTask, this, task, std::ref(result1), std::ref(result2)), task, result1, result2);
    workers.emplace_back(worker);
}
for (auto& w : workers) {
    std::get<0>(w).join();
    std::cout << "Task=" << std::get<1>(w) << " Result1=" << std::get<2>(w) << " Result2=" << std::get<3>(w) << std::endl; 
}

我认为问题在于在嵌套容器中引用 std::thread 导致 workers.emplace_back(worker); 无效。我尝试在几个地方使用 std::move 没有成功。

workers.emplace_back(worker); 导致以下错误:

external/libcxx/include/thread:268:5: 错误:'std::__1::thread::thread(const std::__1::thread&)' 是私有的 线程(常量线程&);

external/libcxx/include/type_traits:887:87: 错误:在此上下文中 sizeof(__is_convertible_imp::__test<_t2>(__is_convertible_imp::__source<_t1>())) == 1

external/libcxx/include/type_traits:851:28: 错误:初始化 'char std::__1::__is_convertible_imp::__test(_Tp) [with _Tp = std::__1::thread]' 的参数 1 模板字符 __test(_Tp);

按照 Daniel 的要求,我整理了一个最小的可编译示例。它在 cpp.sh 上正确编译和执行:

#include <iostream>
#include <thread>
#include <tuple>
#include <vector>

using Task = std::string;
using Result1 = std::string;
using Result2 = std::string;

class Slave {
    private:
    void performTask(Task task, Result1& result1, Result2& result2) {
        if (task == "A") {
            result1 = "A1";
            result2 = "A2";
        } else if (task == "B") {
            result1 = "B1";
            result2 = "B2";
        } else {
            result1 = "C1";
            result2 = "C2";
        }
    }
    public:
    std::vector<Task> tasks;
    std::vector<std::tuple<Task, Result1, Result2>> performParallel() {
        std::vector<std::tuple<Task, Result1, Result2>> results;
        std::vector<std::tuple<std::thread, Task, std::unique_ptr<Result1>, std::unique_ptr<Result2>>> workers;
        for (auto const& task : tasks) {
            std::unique_ptr<Result1> result1(new Result1);
            std::unique_ptr<Result2> result2(new Result2);
            std::tuple<std::thread, Task, std::unique_ptr<Result1>, std::unique_ptr<Result2>> worker = std::make_tuple(std::move(std::thread(&Slave::performTask, this, task, std::ref(*result1), std::ref(*result2))), task, std::move(result1), std::move(result2));
            workers.emplace_back(std::move(worker));
        }
        for (auto& w : workers) {
            std::get<0>(w).join();
            std::tuple<Task, Result1, Result2> result = std::make_tuple(std::get<1>(w), *std::get<2>(w), *std::get<3>(w));
            results.emplace_back(result);
        }
        return results;
    }
};

int main() {
    Slave slave;
    slave.tasks = { "A", "B", "C" };
    std::vector<std::tuple<Task, Result1, Result2>> results = slave.performParallel();
    for (auto const& r : results) {
        std::cout << "Task=" << std::get<0>(r) << " Result1=" << std::get<1>(r) << " Result2=" << std::get<2>(r) << std::endl; 
    }
}

执行输出:

任务=A 结果1=A1 结果2=A2

任务=B 结果1=B1 结果2=B2

任务=C 结果1=C1 结果2=C2

【问题讨论】:

  • 底部循环中的ts 应该是ws 对吧?
  • 另外,不要害怕使用结构或类。包含 4 个项目的元组急需名称。
  • 请编辑您的问题以包含您可能看到的任何错误消息。
  • 请尝试提供一个最小的、可编译的、可验证的示例以及对预期行为和实际观察到的行为的描述。
  • 调用 workers.emplace_back(std::move(worker)) 适用于 MSVC 2015。您使用的是什么编译器?

标签: c++ multithreading c++11 stdthread


【解决方案1】:

线程不是可复制构造的,因此出现错误'thread(const thread&amp;)' is private

线程 可移动构造,所以你可以使用std::move emplace_back 一个右值引用:

workers.emplace_back(std::move(worker));

虽然这将解决眼前的问题,但请注意您还有一个更大、更险恶的问题 - 即悬空引用

您正在将对临时对象的引用传递给您的线程函数,(然后使用Result1Result2副本创建一个元组)

Result1 result1;
Result2 result2;

std::tuple<std::thread, Task, Result1, Result2> worker = 
    std::make_tuple(
        std::thread(
            &Slave::performTask, 
            this, 
            task, 
            std::ref(result1),  <-- here you pass a reference to a local variable
            std::ref(result2)), <-- 
        task, 
        result1,   <-- here you copy construct the tuple member from a local var
        result2);  <--

您的线程将引用对result1result2 的悬空引用,并且可能会出现段错误。

可以先使用空线程对象创建元组,然后创建传递对元组成员的引用的线程,但我不建议这样做。

更好的设计是使用一些封装,将你的线程和结果变量打包到自己的类中

struct Worker
{
    Worker(Task& task)
        : task(task)
        , _thread(&Worker::performTask, this)
    {
    }

    void performTask()
    {
        result1 = task.foo();
        result2 = task.bar();
    }

    void join()
    {
        _thread.join();
    }

    Task    task;
    Result1 result1;
    Result2 result2;

private:
    std::thread _thread;
};

现在您不必尝试使用Result 变量创建元组将引用传递给线程元组成员。

【讨论】:

  • 你提出了一些好的观点。我相信我在整理可编译的代码示例时解决了其中的一些问题。看看我添加到问题中的代码。
  • 我真的建议你拥抱 OOD 并将你的工人封装到一个工人类中。这将使您的代码更容易推理。您也不一定需要使您的 Result 对象指针,以确保它们的生命周期超过您的循环。创建你的工人会简单得多 - workers.emplace_back(task); - 完成。
  • 我仍在考虑您关于重构代码以改进封装的建议。我的主要犹豫是创建一个仅在这种特定情况下使用的类,但也许这是正确的方法。
  • @Snooze 为什么你创建一个类?您将“完成工作”所需的所有内容封装到一个“工人”类中。
  • 我明天会尝试这种方法,看看结果如何。问题中的代码示例只是一个简单的示例,因为我无法共享原始代码。希望重构能够消除我在 AOSP 上看到的编译器错误。感谢您的反馈!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-23
  • 2015-06-02
  • 2012-05-21
  • 2018-10-27
相关资源
最近更新 更多