系列文章目录
-进程
进程是运行着的程序
进程内存空间分配:略
如果主进程结束而子进程未结束,则Linux内核会将该子进程的父进程ID改为1(init进程),
-前言
void funcname(const A& v);
std::thread(funcname, value); // 即使函数的形参是引用类型也会发生拷贝构造,
除非:
void funcname(A& v);
std::thread(funcname, std::ref(value)); // 这样value对象就是主线程中的对象
base类
#include <iostream>
#include <thread>
class Base
{
public:
int num = 1; // 类内初始化
Base()
{
std::cout << "默认构造函数Base()运行 "
<< " this: " << this << " id= "
<< std::this_thread::get_id() << std::endl;
}
Base(const Base& b)
{
std::cout << "拷贝构造函数Base(&)"
<< " this: " << this << " id= "
<< std::this_thread::get_id() << std::endl;
}
~Base()
{
std::cout << "析构函数~Base()"
<< " this: " << this << " id= "
<< std::this_thread::get_id() << std::endl;
}
void operator()(int num)
{
std::cout << "运算符重载,子线程执行: "
<< " this: " << this << " id= "
<< std::this_thread::get_id() << std::endl;
}
void thdjob(int n);
};
线程执行函数
- 普通函数
void thdjob(int n) { std::cout << "子线程执行:" << std::this_thread::get_id() << std::endl; }
- 类的成员函数
void Base::thdjob(int n) { std::cout << "子线程执行:" << std::this_thread::get_id() << std::endl; }
结果分析
int main()
{
Base b;
// 在第一个参数为普通函数的情况下,引用?
// 当第一个参数为类的成员函数时,则子线程和主线程用的不是同一个对象
// 若为引用或地址则为同一个对象
std::thread thd(&Base::thdjob, b, 4);
}
小结,行为总结
- std::thread中,即使线程函数的形参是引用类型也会进行对象拷贝
- std::thread(…)中假定所有实参都为右值
void thdjob(const Base& b); // 必须是const引用,并且会发生
- 无对象拷贝的方式:
- 引用
void thdjob(Base& b); // 子线程中的对象b与主线程中的是同一个,自然无对象拷贝 // 需要确保子线程在使用b时,主线程不会将其销毁 std::thread th(thdjob, std::ref(b)); // std::ref将b变为引用类型
- 指针
void thdjob(Base* b); // 用地址传递自然都是同一个对象 std::thread th(thdjob, &b);
- 引用
- 发生对象拷贝
- 2次拷贝,主线程子线程各一次
发生两次对象拷贝,第一次发生在主线程,将b对象拷贝到th,第二次发生在子线程,将th中的右值对象拷贝到形参void thdjob(Base b); std::thread th(thdjob, b);
默认构造函数Base()运行 this: 0x7ffcd8b7f014 id= 139770366710720 ---------- 拷贝构造函数Base(&) this: 0x558e2431d2c8 id= 139770366710720 拷贝构造函数Base(&) this: 0x7f1ed29fed74 id= 139770359445056 this: 0x7f1ed29fed74子线程执行:139770359445056
- 1次拷贝
// 子线程发生一次对象拷贝 void thdjob(Base b); // std::ref 主线程无拷贝 std::thread th(thdjob, std::ref(b));
默认构造函数Base()运行 this: 0x7ffd5c67123c id= 139786634453952 ---------- 拷贝构造函数Base(&) this: 0x7f229c3fed74 id= 139786627053120 this: 0x7f229c3fed74子线程执行:139786627053120
※推荐的方式※// 子线程无拷贝 void thdjob(const Base& b); // 主线程进行1次对象拷贝 std::thread th(thdjob, b);
解析:主线程创建b的拷贝,即使主线程结束也是安全的;子线程引用b的拷贝,当子线程结束时负责析构该对象。默认构造函数Base()运行 this: 0x7ffd26fb3764 id= 140183106712512 ---------- 拷贝构造函数Base(&) this: 0x563e24eee2c8 id= 140183106712512 this: 0x563e24eee2c8子线程执行:140183099930176
- 2次拷贝,主线程子线程各一次
-c++11线程对象创建后既不join()也不detach()的后果
c++11中,创建对象(std::thread)后有两种状态:
joinable
nonjoinable
线程对象通过默认构造函数构造后状态为nonjoinable; 线程对象通过有参构造函数创建后状态为join able。joinable状态的线程对象被调用join()或detach()会变为nonjoinable状态。
线程对象析构
// thread类中的析构函数定义:
~thread()
{
if(nonjoinable){
std::terminate();
}
}
线程对象析构时,会判断线程的状态。如果线程处于join able状态时,会调用terminate()函数直接令程序退出。
也就是说,创建一个可运行(创建时传入线程函数)线程对象后,必须对该对象进行处理,要么调用join()要么调用detach(),否则线程对象析构时程序将直接退出。
-
附注代码
※推荐的方式※
void testfn(const Base & b)
{
std::cout << "this = " << &b << " tid = " << std::this_thread::get_id()
<< std::endl;
usleep(10000000);
std::cout << b.num << std::endl;
}
void subth()
{
Base B;
std::cout << "subth Base B this = " << &B
<< " tid = " << std::this_thread::get_id() << std::endl;
std::thread th(testfn, B);
th.detach();
}
int main()
{
std::cout << "main tid: " << std::this_thread::get_id() << std::endl;
std::thread th(subth);
th.detach();
std::cout << "main sleep" << std::endl;
while(1);
}
main tid: 140303695139776
main sleep
默认构造函数Base()运行 this: 0x7f9aff7fed6c id= 140303688267328
subth Base B this = 0x7f9aff7fed6c tid = 140303688267328
拷贝构造函数Base(&) this: 0x7f9af8000b78 id= 140303688267328
析构函数~Base() this: 0x7f9aff7fed6c id= 140303688267328
this = 0x7f9af8000b78 tid = 140303679874624
1
析构函数~Base() this: 0x7f9af8000b78 id= 140303679874624