【问题标题】:In C++ multithreading applications, should I pass lambda or function with parameters?在 C++ 多线程应用程序中,我应该传递 lambda 还是带参数的函数?
【发布时间】:2017-12-18 18:17:23
【问题描述】:

我刚刚开始学习 C++ 中的多线程... t1t2 有区别吗?

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mutexCout;

//prints the value of x, and then increments it
//param: int value to display and increment
void foo (int& x)
{
    std::lock_guard<std::mutex> guard_(mutexCout);
    std::cout << "x is " << x << "\n";
    ++x;
}

//testing different ways to call a function with reference
int main() 
{
    int x = 5;
    //is t1 different from t2???
    std::thread t1 ([&] {foo(x)};
    std::thread t2 (foo, std::ref(x));
    {
        std::lock_guard<std::mutex> guard_(mutexCout);
        std::cout << "x is " << x << "\n";
    }

    //added after posting the question
    t1.join();
    t2.join();

    return 0;
}

【问题讨论】:

  • 这个问题最好在Code Review 提出。我会选择更简单的构造,即 t2。 t1 需要一个特殊的语言特性 (lambda),如果没有明显的好处,为什么要使用它。
  • @zett42,我会说 t1 初始化程序是简单的初始化程序。 (忽略语法错误)t2 需要一个特殊的库功能(std::ref),如果没有明显的好处,为什么要使用它呢?

标签: multithreading c++11 lambda


【解决方案1】:

我假设您已通过调用加入来修复它。

使用[&amp;] 捕获超过当前范围或在不同线程中运行的 lambda 通常是一个坏主意:在这两种情况下,您应该有足够少的共享状态,明确说明您正在共享的内容,如 [&amp;x] 是这不是一个严重的开销问题,并且会防止您由于看似无害的拼写错误或命名错误而以危险的方式意外共享错误数据。

通常,在应用程序中使用原始 C++ std 线程 API 仅在应用程序小而简单且线程有限时才有用。过了这一点,您将需要线程池、延续、信号等。C++ 线程原语足以编写这些,但它们不提供这些。

我发现,当我编写自己的包装器时,期望调用者传入一个空值可调用对象(或只包含它期望线程框架提供的参数)比 std 所做的要理智得多您还可以在其中传递参数。在 lambda 语法中添加 后,这变得更加可行,它允许移动参数和计算绑定。

所以在“真实”代码中,我的代码如下所示:

my_future<void> r = some_thread_pool.add_task([&x] {foo(x)});

【讨论】:

  • 感谢您提供的“实时”示例。我知道some_thread_pool 是类的一个实例,但是my_future 的类型是什么?
  • @Anton 在我的例子中,它是一个支持像.then( more code ) 这样的延续的未来,它反过来又返回另一个my_future。基本上,std::future 不足以满足我的需求。其他人可能需要可多次等待的期货。这些东西可能会在 C++ 的未来迭代中出现,但现在我必须自己编写它们。
  • @Anton,“未来”是一个用于将结果从一个线程传递到另一个线程的对象。我不具体了解 C++ 标准库,但在许多编程语言/库中,有一种称为“未来”或“承诺”的东西,生产者线程可以在其中存储一个值,而消费者可以从中获取它。通常,如果生产者尚未存储该值,则有一种方法允许消费者等待该值。它有点像一个 1 深的阻塞队列,只能使用一次。它也可能有办法将异常从一个线程传递到另一个线程。
  • 销毁可连接的std::thread 具有明确定义的行为:terminate()。可以肯定的是,这不是一个特别有用的行为......
  • @t.c 正在考虑 post detatch,我的错
【解决方案2】:

两者在运行时行为方面几乎没有区别,但我会非常小心地使用通过引用和线程进行的通用捕获。例如。上面的两段代码都将地址传递给main 中的堆栈,并且当线程通过该地址间接执行时,不能保证main 仍在执行。因此未定义的行为。使用 lambdas 中的通用引用捕获,这更有可能是偶然发生的。使用值捕获或显式捕获更安全。

我不确定我是否同意上面的评论,即 lambda 正在使用更复杂的语言功能。在这两种情况下,都必须打包一个结构来包含参数状态以通过线程边界。对于这种情况,由于调用只是将一个指针传递给线程函数,因此可以在没有中间结构的情况下完成。 (lambda 版本可能必须使用结构。)但是,在参数转发等方面仍然存在相当多的 C++ 复杂性。

如果你真的对正在发生的事情感兴趣,最好查看从一个好的编译器生成的程序集,对于像这样的小例子来说,这是非常可行的。 (并且与老式 C 中的直接 pthreads 代码相比也可能具有启发性。)我现在没有时间这样做,但我很好的猜测是,与正确性、可移植性和代码可读性问题相比,这些差异并不重要.

我确实发现 lambdas 是一个非常复杂的包,无论它们使代码更容易还是更难阅读。如果只是将一些参数传递给线程函数,那么 lambda 似乎将更难阅读,因为必须验证 lambda 的大括号内没有发生任何花哨的事情。 (而且它是 C++,因此通过运算符重载等,看起来像函数调用的事情可能非常复杂。)如果将整个执行放在 lambda 中并且它很小,因此不必去查看单独的线程函数,那么它可能会使代码更易于阅读。

【讨论】:

  • Lambda 不会使代码更容易或更难阅读。 程序员就是这么做的。 Lambda 只是程序员可以用来实现这两种效果的众多工具之一。
猜你喜欢
  • 1970-01-01
  • 2022-06-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-10-05
  • 1970-01-01
  • 2010-10-25
  • 1970-01-01
相关资源
最近更新 更多