thread:位于头文件<thread>中,是一个用于创建线程的类。详情可以在头文件(ctrl+鼠标左键)点进去看。
创建线程的模板:
可执行(调用)对象一般包括:普通函数、函数指针、类内成员函数、仿函数,即重载了operator()运算符的类对象、匿名函数,即Lambda表达式等。
std::thread 对象名(可执行对象, 可执行对象的参数);如下:
void myprint() { cout << "执行myprint这个函数" << endl; }
void myprint1(const int &num, const int &a) { cout << "执行myprint1这个函数" << " num的值为:" << num <<endl; }
int main() {
std::thread obj(myprint);
obj.join();
int num = 1, a = 2;
std::thread obj1(myprint1, num, a);
obj1.join();
return 0;
}
std::thread 对象名(&类名::类内函数名, &类对象 , 参数); //这里对类对象引用表示可以对该对象的数据进行修改,若不用引用则会调用拷贝构造函数。
主要函数:
join(); 用于阻塞主线程,使得主线程在该线程运行结束后再运行。用法如上。
detach(); 用于分开主线程和子线程,使其同时独立运行。(用法同join)(慎用,不然可能会传入地址后主线程运行结束而子线程还在运行导致异常)注意:主线程结束后进程结束,所以若用detach,主线程结束后若子线程还未结束的话会在后台中进行直到结束,由运行库释放。
joinable();同一个线程只能调用一次join或者detach,joinable可以判断该线程是否已经使用过join或者detach,若否,则返回true,若是,则返回false;一般用于如下情况:
std::thread obj(myprint);
if(obj.joinable()){ obj.join(); }
std::ref(可执行对象的参数);正常情况下传进子线程的带引用的参数都是通过拷贝、复制出一个新的参数传进线程中,在线程中改变该参数并不能影响到线程外的该参数,ref用于告诉(我也不知道告诉编译器还是什么)这个参数在线程内改变可以影响到线程外的该参数(类似于地址传递)。如下:
void myprint1(int &num) {
num++;
cout << "执行myprint1这个函数" << "num的值为:" << num <<endl;
}
int main() {
int num = 1;
std::thread obj2(myprint1, ref(num));
obj2.join();
cout << num << endl; //此时输出2而不是1
return 0;
}
this_thread::get_id() / get_id();在同一进程中,所有线程的id都是唯一的,可用于获取某一线程的id。位于可执行对象内时使用this_thread::get_id();位于线程实现后,join()之前时使用对象名.get_id();
void myprint() {
cout << "执行myprint这个函数" << endl;
cout << "这个线程的id为:" << this_thread::get_id() << endl;
}
int main() {
std::thread obj(myprint);
obj.get_id();
obj.join();
return 0;
}
常见的可执行对象:
普通函数:如上。
类:通过重载类中的()运算符来实现。
class B {
public:
void operator()(){ cout << "子线程开始" << endl; }
};
int main() {
B b;
thread obj(b);
obj.join();
return 0;
}
类的成员函数:thread 线程名(&类名::函数名, 类对象, 函数参数);如下(一定要加&哦)
class C {
public:
void xiancheng() { cout << "子线程运行" << endl; }
};
int main() {
C c;
thread obj1(&C::xiancheng, c);
obj1.join();
return 0;
}
使用detach()容易犯的错:由于detach会使子线程与主线程分开,所以操作不当的话会导致传入参数由于主线程结束运行而销毁,但子线程还未复制该参数,导致传入的参数地址无效,发生异常。以下是使用过detach时可能会存在的一些问题:
类对象
函数参数中存在引用(&):若是不用引用,则会在运行到子线程时在主线程中先拷贝一遍函数参数,再将拷贝后的参数传入子线程,但是thread会再拷贝一编参数,最后运用拷贝后的参数,即若不使用引用,则参数传递时会拷贝两次。但若是使用引用,则参数只会在thread中被拷贝一次(即虽然是引用,但实质仍是值传递)。下面的例子可以表明该观点:
#include<iostream>
#include<vector>
#include<map>
#include<thread>
using namespace std;
class A {
private:
int a;
public:
A() { cout << "调用无参构造函数" << " 此时的线程id为:" << this_thread::get_id() << endl; }
A(const int& b):a(b) { cout << "调用有参构造函数" << " 此时的线程id为:" << this_thread::get_id() << endl; }
A(const A& A1):a(A1.a) { cout << "调用浅拷贝构造函数" << " 此时的线程id为:" << this_thread::get_id() << endl; }
~A() { cout << "调用析构函数" << " 此时的线程id为:" << this_thread::get_id() << endl; }
};
void myprint(const A &a) { //参数使用引用
cout << "子线程的id为:" << this_thread::get_id() << endl;
}
void myprint2(const A a) { //参数不使用引用
cout << "子线程的id为:" << this_thread::get_id() << endl;
}
int main() {
cout << "主线程id为:" << this_thread::get_id() << endl;
A ob;
thread obj(myprint, ob);
obj.join();
thread obj1(myprint2, ob);
obj1.join();
return 0;
}
函数参数中存在指针时:若是用指针,则是地址传递,若此时主线程结束,则该指针指向的地址会被释放,导致子线程中的指针变为NULL或野指针,发生异常。
函数传入实参要避免隐式类型转换:当函数实参需要隐式类型转换时,会在子线程中调用主线程的函数,若子线程中未开始类型转换时主线程结束,则类型转换所需函数会被销毁,子线程调用时会返回异常。所以在使用时,要避免隐式类型转换的发生,可以主动强制类型转换。