多线程
创建
简单来说每个线程都和执行一个main函数是一样的(main就是一个线程,只不过这个线程中main是它的入口函数),也需要一个入口函数,并且在入口函数结束之后也会自动退出。
在为一个线程创建了一个std::thread
对象后,需要等待这个线程结束。
#include <thread>
// 将do_some_work作为入口函数
void do_some_work();
std::thread my_thread(do_some_work);
// 传入类,可以对线程对象的符号函数进行重载
class background_task
{
public:
void operator()() const
{
do_something();
do_something_else();
}
};
background_task f;
std::thread my_thread(f);
// 传入lambda函数
std::thread my_thread([]{
do_something();
do_something_else();
});
// 传入struct
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
do_something(i); // 1. 潜在访问隐患:悬空引用
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2. 不等待线程结束
} // 3. 新线程可能还在运行
附录
void operator()() const 解析
第一个()是运算符的名称 – 它是在对象上使用()时调用的运算符. 第二个()是用于参数的
#include <iostream>
class Test {
public:
void operator()(int a, int b) {
std::cout << a + b << std::endl;
}
};
int main() {
Test t;
t(3,5);//相当于调用了operator()(int a, int b)
return 0;
}
// 输出结果
// 8
lambda表达式
lambda表达式的一系列语义都需要封闭在括号中,还要以方括号作为前缀:
// 简单示例
[]{ // lambda表达式以[]开始
do_stuff();
do_more_stuff();
}(); // 表达式结束,可以直接调用
// 作为函数参数
std::vector<int> data=make_data();
std::for_each(data.begin(),data.end(),[](int i){std::cout<<i<<"\n";});
// 带return
std::condition_variable cond;
bool data_ready;
std::mutex m;
void wait_for_data()
{
std::unique_lock<std::mutex> lk(m);
cond.wait(lk,[]()->bool{
if(data_ready)
{
std::cout<<”Data ready”<<std::endl;
return true;
}
else
{
std::cout<<”Data not ready, resuming wait”<<std::endl;
return false;
}
});
}
可以像下面这样显示指定返回类型:
[] (int x, int y) -> int { int z = x + y; return z; }
获取变量
lambda函数使用空的[](lambda introducer)就不能引用当前范围内的本地变量;其只能使用全局变量,或将其他值以参数的形式进行传递。当想要访问一个本地变量,需要对其进行捕获(拷贝)。最简单的方式就是将范围内的所有本地变量都进行捕获,使用**[=]**
就可以完成这样的功能。函数被创建的时候,就能对本地变量的副本进行访问了。
// 注意,以下函数的返回值是lambda函数类型std::function,模板函数<int(int)>
// 括号里是传入参数类型,括号外是lambda返回值类型参数
std::function<int(int)> make_offseter(int offset)
{
return [=](int j){return offset+j;};
}
// 调用
int main()
{
std::function<int(int)> offset_42=make_offseter(42);
std::function<int(int)> offset_123=make_offseter(123);
std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
std::cout<<offset_42(12)<<”,“<<offset_123(12)<<std::endl;
}
使用**[&]**
对所有本地变量进行引用(不进行拷贝,所以获得的变量值为当前环境的值)
int main()
{
int offset=42; // 1
std::function<int(int)> offset_a=[&](int j){return offset+j;}; // 2
offset=123; // 3
std::function<int(int)> offset_b=[&](int j){return offset+j;}; // 4
std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl; // 5
offset=99; // 6
std::cout<<offset_a(12)<<”,”<<offset_b(12)<<std::endl; // 7
}
join或detach
先给一个不好的例子,在detach之后,主线程结束,而子线程还在运行,并且访问了主线程的变量,导致程序崩溃
// 传入struct
struct func
{
int& i;
func(int& i_) : i(i_) {}
void operator() ()
{
for (unsigned j=0 ; j<1000000 ; ++j)
{
do_something(i); // 1. 潜在访问隐患:悬空引用
}
}
};
void oops()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread my_thread(my_func);
my_thread.detach(); // 2. 不等待线程结束
} // 3. 新线程可能还在运行
所以在detach时,一定要确保不会用主线程的变量
join
如果需要等待线程,相关的std::thread
实例需要使用join()。使用my_thread.join()
,就可以确保局部变量在线程完成后,才被销毁。
确保在主线程异常退出时,还可以执行join()
class thread_guard
{
std::thread& t;
public:
explicit thread_guard(std::thread& t_):
t(t_)
{}
~thread_guard()
{
if(t.joinable()) // 1
{
t.join(); // 2
}
}
thread_guard(thread_guard const&)=delete; // 3
thread_guard& operator=(thread_guard const&)=delete;
};
struct func; // 定义在清单2.1中
void f()
{
int some_local_state=0;
func my_func(some_local_state);
std::thread t(my_func);
thread_guard g(t);
do_something_in_current_thread();
} // 4
在这段代码中,拷贝构造函数和拷贝赋值操作被标记为=delete
,是为了不让编译器自动生成它们。直接对一个对象进行拷贝或赋值是危险的,因为这可能会弄丢已经加入的线程。通过删除声明,任何尝试给thread_guard对象赋值的操作都会引发一个编译错误。
detach
调用std::thread
成员函数detach()来分离一个线程。之后,相应的std::thread
对象就与实际执行的线程无关了,并且这个线程也无法加入:
std::thread t(do_background_work);
t.detach();
assert(!t.joinable());
为了从std::thread
对象中分离线程(前提是有可进行分离的线程),不能对没有执行线程的std::thread
对象使用detach(),也是join()的使用条件,并且要用同样的方式进行检查——当std::thread
对象使用t.joinable()返回的是true,就可以使用t.detach()。
附录
第2章 线程管理 - 2.1 线程管理的基础 - 《C++并发编程(中文版)(C++ Concurrency In Action)》 - 书栈网 · BookStack