0、class thread
功能:创建线程
观察器 | |
检查线程是否可合并,即潜在地运行于平行环境中 (公开成员函数) | |
返回线程的 id(公开成员函数) | |
返回底层实现定义的线程句柄(公开成员函数) | |
返回实现支持的并发线程数(公开静态成员函数)操作 | |
操作 | |
等待线程完成其执行(公开成员函数) | |
容许线程从线程句柄独立开来执行(公开成员函数) | |
交换二个 thread 对象(公开成员函数) | |
移动 thread 对象 |
注:
detach():主线程和子线程分离,主线程不必等待子线程运行完毕,失去了关联,此时子线程会驻留在后台运行,这个子线程相当被C++运行时库接管,由运行时库其负责相关清理(守护进程)。一旦detach()后不能再使用join(),否则会出现异常。detach()一般用的比较少,主线程一般都需要使用join()来等待子线程的运行结束后再一起结束。
joinable():判断是否可以成功使用join()或者detach()。返回true(可以是join或者detach)或者false(不能join或者detach)。
例如:
void myObj(){}
void myDetach(){}
thread obj(obj); // 创建线程
obj.join(); // 阻塞线程
// obj.detach(); // 分离线程
1、使用类作为线程参数
detach()主线程和子线程分离时可能出现主线程执行完了子线程还未执行完的错误。
一旦调用detach,那么主线程执行完毕,如果主线程中的对象不在了,而thread调用了类,则如果该对象被复制(传值),即使主线程结束,也没问题。但是如果该对象没有被复制(传引用和传指针),则可能会出错,因此,使用detach子线程的时候不要使用传引用和传指针,否则可能会出错。
在创建线程的同时构建临时对象的方法传递参数是可行的,即该临时比变量又复制了一个,如使用sting(str)。
例:
class A
{
public:
int m_i;
A(int a) :m_i(a){
cout << "构造函数" << endl;
}
A(const A &a) : m_i(a.m_i) {
cout << "复制构造函数" << endl;
}
~A() {}
};
int main()
{
// 传递临时对象作为线程参数
int mvar = 1;
int &mvarT = mvar;
char buf[] = "china";
// 在这里使用string(buf)的临时对象,保证主线程结束后该变量被复制了,
// 否则会出现在主线程结束后该对象也的生命周期也结束了
// 如果直接使用buf则在主线程结束后,buf也消失了
thread myObj(myPrint, mvar, string(buf));
//myObj.join();
myObj.detach();
int mvar = 22;
return 0;
}
注意:
在使用detach()时:
- 若传递int这种简单类型参数,建议都是值传递,不要用引用传递,防止节外生枝。
- 如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象,然后在函数里,用引用来接,否则系统还会多构造一次对象。
- 建议不使用detach,只是使用join(),可防止局部变量失效导致对内存的非法引用。
2、在创建线程的时候不是使用复制,而是使用引用的情况
从上面可以看出在创建线程的时候使用引用时又是使用detach()时可能会出现问题,那样有时候又必须使用引用时,应该怎么办。
(1)函数
a、获取线程id:
std::this_thread::get_id();
b、std::ref()函数:
std::ref()函数可以直接使用引用,而不是对象的拷贝,即可以操作同一个地址。
例:
class A
{
public:
mutable int m_i; // mutable可修改的,即使是在const中
A(int i):m_i(i) {
cout << "构造函数,m_i = " << m_i << endl;
}
A(const A &a):m_i(a.m_i){
cout << "拷贝构造函数中线程thread_id = " << this_thread::get_id() << endl;
}
~A() {
cout << "析构函数中线程thread_id = " << this_thread::get_id() << endl;
}
void thread_work(int num){
cout << "子线程中的thread_work中,线程thread_id = " << this_thread::get_id() << endl;
}
void operator() (int num) {
cout << "operator中线程thread_id = " << this_thread::get_id() << endl;
cout << "operator中,num = " << num << endl;
}
};
void myPrint(const A &pmyPrint)
{
pmyPrint.m_i = 66;
cout << "子线程中修改m_i = " << pmyPrint.m_i << endl;
}
int main()
{
A myObj(10);
cout << "主线程thread_id = " << this_thread::get_id() << endl;
//thread thr(myPrint, myObj); // 复制调用
thread thr(myPrint, ref(myObj)); // 引用调用
cout << "主线程中m_i = " << myObj.m_i << endl;
thr.join();
system("pause");
return 0;
}
如果直接使用thread thr(myPrint, myObj);则会拷贝一个对象,也会调用拷贝构造函数,使得子线程的修改对主线程没有影响,即使在子线程中是使用引用来接受对象的。
但是如果使用了thread thr(myPrint, ref(myObj));则是直接使用引用来传参的。修改的是同一个变量。
3、使用智能指针作为线程参数
class A
{
public:
mutable int m_i; // mutable可修改的,即使是在const中
A(int i):m_i(i) {
cout << "构造函数,m_i = " << m_i << endl;
}
};
void myPrint(unique_ptr<int> pzn)
{
cout << "子线程中的智能指针" << pzn << endl;
}
int main()
{
A myObj(10);
cout << "主线程thread_id = " << this_thread::get_id() << endl;
unique_ptr<int> myp(new int(100));
cout << "主线程中的智能指针" << myp << endl;
thread thr(myPrint, move(myp)); // move()将参数转换为右值
。。。
}
从结果中可以看出,在创建线程时如果使用move()传递智能指针,则在子线程中的智能指针不是复制,而是同一个地址,即同一个值,类似引用。
4、使用成员函数指针做线程函数
(1)直接传值
clas A{
public:
mutable int m_i; // mutable可修改的,即使是在const中
A(int i):m_i(i) {
cout << "构造函数中thread_id = " << this_thread::get_id() << endl;
cout << "构造函数,m_i = " << m_i << endl;
}
A(const A &a):m_i(a.m_i){
cout << "拷贝构造函数中线程thread_id = " << this_thread::get_id() << endl;
}
~A() {
cout << "析构函数中线程thread_id = " << this_thread::get_id() << endl;
}
void thread_work(int num){
cout << "子线程中的thread_work中,线程thread_id = " << this_thread::get_id() << endl;
}
void operator() (int num) {
cout << "operator中线程thread_id = " << this_thread::get_id() << endl;
cout << "operator中,num = " << num << endl;
}
};
int main()
{
。。。
A myObj(10);
thread thr(&A::thread_work, myObj, 666);
。。。
}
从结果中可以看出,在创建线程时直接传值得时候会调用该类的拷贝构造函数。
(2)传引用
thread thr(&A::thread_work, &myObj, 666);
从结果可以看出,在创建线程的时候如果传入的是该类的引用,则不会调用该类的构造函数。
(3)使用ref()
thread thr(&A::thread_work, ref(myObj), 666);
从结果可以看出,在创建线程的时候如果使用ref()则传输的也是该类的引用。
(4)使用operator()
class A
{
public:
mutable int m_i; // mutable可修改的,即使是在const中
A(int i):m_i(i) {
cout << "构造函数中thread_id = " << this_thread::get_id() << endl;
cout << "构造函数,m_i = " << m_i << endl;
}
A(const A &a):m_i(a.m_i){
cout << "拷贝构造函数中线程thread_id = " << this_thread::get_id() << endl;
}
~A() {
cout << "析构函数中线程thread_id = " << this_thread::get_id() << endl;
}
void thread_work(int num){
cout << "子线程中的thread_work中,线程thread_id = " << this_thread::get_id() << endl;
}
void operator() (int num) {
cout << "operator中线程thread_id = " << this_thread::get_id() << endl;
cout << "operator中,num = " << num << endl;
}
};
int main()
{
A myObj(10);
cout << "主线程thread_id = " << this_thread::get_id() << endl;
thread thr(myObj, 15);
thr.join();
}
从结果中可以看出,如果使用类中重载了括号(operator()),则在创建线程的时候如果传入了该类,并给该类传值,则会调用到operator()。