【问题标题】:Using smart pointers within multi-threaded application在多线程应用程序中使用智能指针
【发布时间】:2020-07-20 18:43:42
【问题描述】:

我需要执行一些计算,这取决于两个或多个步骤,如下所示:

class A
{
public:
    double step1() { return 2.5; }
};

class B
{
public:
    double step2() { return 1.2; }
};

class Result
{
public:
    Result(std::shared_ptr<A> _a, std::shared_ptr<B> _b) : a(_a), b(_b) {};

    double getResult() { return a->step1() + b->step2(); }

private:
    std::shared_ptr<A> a;
    std::shared_ptr<B> b;
};

实际上,第 1 步和第 2 步需要多态行为,因此这些(共享)指针将指向“接口”类,但这里的细节并不重要。

现在,getResult() 中的最终计算也需要多态行为,因此我创建了一个指向 Result 的(唯一)指针,创建了一个调用 getResult() 的 lambda,并将该 lambda 传递给我的线程,如下所示:

void run_multi_threaded_calculation()
{
    auto result = create_result_unique_ptr();

    const int nThreads = 4;
    std::vector<double> save(nThreads);

    auto task = [&](int n) {
        // Preprocessing before getResult()
        save[n] = n * result->getResult();
    };

    std::vector<std::thread> threads;
    threads.reserve(nThreads);
    for (size_t i = 0; i < nThreads; ++i)
    {
        threads.push_back(std::thread(task, i));
    }

    for (auto& th : threads)
        th.join();

    for (const auto& s : save)
        std::cout << s << '\n';
}

问题 1: 我是否使用了正确的智能指针和 lambda 捕获配置,例如unique_ptrResultshared_ptrAB?经过一些猜测并检查更改智能指针类型后,上述编译(但如果Result 中的abunique_ptr,则不会编译),但我不确定这是否是最好的方法接近这个。

问题 2: 如果我将 lambda 替换为等效的(或者我认为的)函数对象,那么我的代码无法编译(错误 C2661: 'std::tuple::tuple':没有重载函数需要 2 个参数)。智能指针是否缺少我的某些东西,或者线程的工作方式,或者我的函数对象定义可能存在一些问题?

以下是相关更改:

class ResultFunctor
{
public:
    ResultFunctor(std::unique_ptr<Result> _result, std::vector<double>& _save) : result(std::move(_result)), save(_save) {};

    void operator() (int n) { save[n] = n * result->getResult(); }

private:
    std::unique_ptr<Result> result;
    std::vector<double>& save;
};

并替换以下行:

void run_multi_threaded_calculation()
{
    // Other stuff is unchaged...

    /*auto task = [&](int n) {
        // Preprocessing before getResult()
        save[n] = n * result->getResult();
    };*/

    auto task = ResultFunctor(std::move(result), save);

    // other stuff is unchanged...
}

【问题讨论】:

  • 为什么result 必须是指针?
  • 这只是演示我的问题的最简单的例子。我的实际程序将具有Result 的多态行为,派生自IResult 基类之类的东西。然后getResult 将使用第 1 步和第 2 步,但会进行一些其他不同的计算。
  • 这可能是协程的一个很好的用例,尤其是在步骤的顺序很重要的情况下。在您的示例中,我认为您不能确定 step1 会在 step2 之前进行评估。
  • 我会研究协程,但你的评论让我意识到我的例子有点误导。实际上,这些步骤不会在同一行中进行评估,而更像是:step1() /*... Do some stuff with the result...*/ step2()

标签: c++ multithreading lambda smart-pointers function-object


【解决方案1】:

您遇到的部分问题是您正在传递unique_ptr。但是,当您尝试将其传递到此处的 std::thread 时,您错过了一个位于 ResultFunctor 类中的move

threads.push_back(std::thread(task, i));

如果您必须使用 unique_ptr,那么您很可能需要在 ResultFunctor 中使用 move c'tor:

class ResultFunctor
{
public:
    ResultFunctor(std::unique_ptr<Result> _result, std::vector<double>& _save) : result(std::move(_result)), save(_save) {};

    void operator() (int n) { save[n] = n * result->getResult(); }

    // Move c'tor rough un-tested example
    ResultFunctor(ResultFunctor&& rf) :
        result(std::move(rf.result)),
        save(rf.save)
    {};

private:
    std::unique_ptr<Result> result;
    std::vector<double>& save;
};

这样您就可以将其移动到 std::thread 的 c'tor 中,该 c'tor 采用 r 值 ref

threads.push_back(std::thread(std::move(task), i));

至少这是我能看到的主要问题,即使它不会导致您当前的错误。

注意:您的 lambda 与您的“等效”仿函数类完全不同。您的 lambda 是通过引用捕获的。然而,当你将它传递给线程时,这并不那么安全,REAL unique_ptr 可能会超出范围并销毁它!

所以这不是一个很好的解决方案。在 c++14 中,我相信您可以使用 move 捕获 unique_ptr:How to capture a unique_ptr into a lambda expression?https://isocpp.org/wiki/faq/cpp14-language#lambda-captures

【讨论】:

  • 这有助于回答我的问题。我进行了您建议的更改并且一切都编译得很好,但是我遇到了一个异常(由于nullptr而导致读取访问冲突),我认为是因为unique_ptr&lt;Result&gt;在其他线程完成之前被其中一个线程删除了,所以我认为应该是shared_ptr。但是,我最终使用带有指向结果的原始指针的函子,因为 Result 的所有权由调用函子的函数管理,而不是函子本身。
  • @CharlieH。好地方,是的,我认为从共享转换为独特你失去了“共享”。因此,当共享指针的引用计数变为零时,它将被销毁:o
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-07-02
相关资源
最近更新 更多