我刚刚开始学习C ++中的多线程... t1
和t2
之间有区别吗?
#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;
}
我假设您通过加入呼叫来修复它。
使用[&]
捕获lambdas比当前作用域更长或者在不同的线程中运行通常是一个坏主意:在这两种情况下你都应该有足够的共享状态明确说明你所共享的内容,如[&x]
不是一个严重的开销问题,并且会因为看似无害的拼写错误或命名错误而阻止您以危险的方式意外地共享错误的数据。
通常,在应用程序中使用原始C ++ std
线程API仅在应用程序小而简单且线程受限时才有用。过去,您需要线程池,延续,信号等.C ++线程原语足以编写它们,但它们不提供这些。
我发现当我正在编写自己的包装器时,期望调用者传入一个nullary可调用的(或者只有一个只需要提供线程框架的参数)比做std
所做的那样你也可以通过参数。随着c++14添加到lambda语法,这变得更加可行,这允许移动参数和计算绑定。
所以在“真实”代码中我的看起来像:
my_future<void> r = some_thread_pool.add_task([&x] {foo(x)});
两者在运行时行为方面差别不大,但我会非常谨慎地使用引用和线程的通用捕获。例如。上面的两段代码都在main
中将一个地址传递给堆栈,当线程间接通过这个地址时,没有任何东西可以保证main
仍在执行。因此未定义的行为。通过lambda中的参考通用捕获,这更可能是偶然发生的。使用值捕获或显式捕获更安全。
我不确定我同意上面的评论,lambda使用更复杂的语言功能。在这两种情况下,都必须打包一个结构来包含参数状态以跨越线程边界。对于这种情况,由于调用只传递一个指向线程函数的指针,因此可以在没有中间结构的情况下完成。 (lambda版本可能必须使用一个结构。)然而,在参数转发等方面仍然存在相当多的C ++复杂性。
如果一个人真的对正在发生的事情感兴趣,最好从一个好的编译器看看生成的程序集,对于像这样的小例子来说这是非常可行的。 (与旧学校C中的直线pthreads代码相比也可能具有指导性。)我现在没时间做这个,但我很好的猜测是,与正确性,可移植性和代码可读性问题相比,差异并不重要。
我发现lambdas是一个非常复杂的包,它是否使代码更容易或更难阅读。如果只是简单地将一些参数传递给线程函数,则看起来lambda将更难以读取,因为必须验证lambda中的花括号内是否发生任何奇特的事情。 (而且它是C ++所以通过运算符重载等等,只是看起来像函数调用的东西可能非常复杂。)如果一个人将整个执行放在lambda中并且它很小,从而节省了必须去看一个单独的线程函数,然后它可以使代码更容易阅读。