快速搭建网站后天台,有什么做调查的网站,网络营销的50种方法,莱芜金点子广告电子版最新一期1、线程的开始与结束
程序运行起来#xff0c;生成一个进程#xff0c;该进程所持有的主线程开始自动运行#xff0c;main主线程运行完所有的代码从main函数中返回表示整个进程运行完毕#xff0c;标志着主线程和进程的死亡#xff0c;等待操作系统回收资源#xff0c;因…1、线程的开始与结束
程序运行起来生成一个进程该进程所持有的主线程开始自动运行main主线程运行完所有的代码从main函数中返回表示整个进程运行完毕标志着主线程和进程的死亡等待操作系统回收资源因为有可能成为孤儿或者僵尸进程所以需要等待。如果创建自己的线程也需要从一个函数开始运行初始函数一旦运行完毕就代表着这个线程运行结束。当主线程运行结束子线程并没有执行完毕也会被操作系统强行终止因为PCB进程控制块资源的回收因此如果需要等待子线程正常执行完毕退出需要让主线程等待所有的子线程执行完毕
set(CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS} -pthread)2、线程的创建
thread库是C11提供的标准库类其构造有很多主要有两种 一种是只提供回调函数的构造一种是提供回调函数 回调函数参数的构造 回调函数回调函数是创建的线程的执行入口
#include iostream
#include threadvoid thread_func1()
{std::cout 子线程开始执行 std::endl;std::cout 子线程执行完毕 std::endl;
}void thread_func2(int arg)
{std::cout 子线程开始执行 std::endl;std::cout arg std::endl;std::cout 子线程执行完毕 std::endl;
}
int main()
{std::thread mythread1(thread_func1);std::thread mythread2(thread_func2, 5);mythread1.join();mythread2.join();std::cout main thread executed finish! std::endl;return 0;
}2.1、join方法
join方法的主要用途就是阻塞主线程等待子线程运行完毕在继续向下执行类似所有线程都要汇聚到这一点主线程才继续向下执行。
如果不适用join方法进行阻塞等待可能会造成异常 #include iostream#include threadvoid thread_func1(){std::cout 子线程开始执行1 std::endl;std::cout 子线程开始执行2 std::endl;std::cout 子线程开始执行3 std::endl;std::cout 子线程开始执行4 std::endl;std::cout 子线程开始执行5 std::endl;std::cout 子线程开始执行6 std::endl;std::cout 子线程执行完毕 std::endl;}int main(){std::thread mythread1(thread_func1);std::cout main thread executed finish! std::endl;return 0;}出现问题的原因是因为主线程已经运行完毕而mythread1是一个main栈帧里分配的一个临时变量一旦执行完毕将会释放这个变量的空间导致线程执行错误
如果将mythread1改成new的形式在堆区分配空间将不会出现错误 #include iostream#include threadvoid thread_func1(){std::cout 子线程开始执行 std::endl;std::cout 子线程执行完毕 std::endl;}int main(){std::thread *mythread1 new std::thread(thread_func1);std::cout main thread executed finish! std::endl;return 0;}不过此时无法看到子线程的输出因为main线程结束子线程将失去控制台的读写权限有点守护线程的味道 一般会让主线程等待子线程运行完毕或者主线程和子线程采用detach进行脱离形成一个新的会话保证所有的线程安全退出。
2.2、detach方法
detach有脱离分离的意思线程调用该方法可以使得主线程和子线程失去上下级关系二者平行。子线程运行完毕会自动退出主线程也无须等待子线程运行完毕。当主线程和子线程并没有产生交集时可以使子线程进行脱离运行完毕自动回收。 举个例子例如用户登录软件或者Web页面当账户验证通过时主线程需要继续响应用户的登录请求而用户的登录日志的保存或者一些其他的日志需要子线程去保存此时主线程和子线程没有任何交集总不可能主线程等待子线程存完日志再响应用户吧因此类似这种情况可以让子线程自动脱离运行完毕自动结束。类似于驻留后台的守护线程脱离的子线程由C运行时库接管当子线程运行完毕时由运行时库负责清理该线程的相关资源。
#include iostream
#include threadvoid thread_func1()
{std::cout 子线程开始执行 std::endl;std::cout 子线程执行完毕 std::endl;
}
int main()
{std::thread mythread1(thread_func1);mythread1.detach();std::cout main thread executed finish! std::endl;return 0;
}一旦调用了detach或者join就不能再调用另外一个方法否则系统会出现异常
2.3、joinable方法
joinable方法主要判断是否可以使用join方法或者detach方法可以返回true不可以返回false一个线程最多只能调用一次join或者detach
#include iostream
#include threadvoid thread_func1()
{std::cout 子线程开始执行 std::endl;std::cout 子线程执行完毕 std::endl;
}int main()
{std::thread mythread1(thread_func1);if(mythread1.joinable()){std::cout joinable() true std::endl;mythread1.join();}else{std::cout joinable() false std::endl;}std::cout ------------------------------ std::endl;if(mythread1.joinable()){std::cout joinable() true std::endl;}else{std::cout joinable() false std::endl;}std::cout main thread executed finish! std::endl;return 0;
}3、线程的其他创建方式
3.1、无参自定义类型创建
线程的创建方式也可以通过传入一个类对象并在类内部对函数运算符()进行重载使用detach或者join都行。
class MyThreadClass1{
public:MyThreadClass1() {}void operator()() {std::cout 子线程开始执行 std::endl;std::cout 子线程执行完毕 std::endl;}
};
void test2()
{MyThreadClass1 myThreadClass1;std::thread mythread1(myThreadClass1);mythread1.join();
}3.2、有参自定义类型创建
有参自定义类型创建时我们需要考虑两种情况
第一种情况是传入的参数是指针或引用、还是普通参数。第二种情况是使用join还是detach方法 结论join方法的话无论传指针或引用、还是普通参数都不会产生任何问题只有detach对传入的参数有影响。
3.2.1、指针或引用类型
class MyThreadClass2{
public:int m_i;MyThreadClass2(int i): m_i(i) {}void operator()() {std::cout 子线程开始执行 std::endl;std::cout 1. m_i m_i std::endl;std::cout 2. m_i m_i std::endl;std::cout 3. m_i m_i std::endl;std::cout 4. m_i m_i std::endl;std::cout 5. m_i m_i std::endl;std::cout 子线程执行完毕 std::endl;}
};
int main()
{int my_i 5;MyThreadClass2 myThreadClass2(my_i);std::thread mythread2(myThreadClass2);mythread2.detach();std::cout main thread executed finish! std::endl;return 0;
}首先自定义类中需要的是一个引用引用的本质是指针常量引用的对象是一个分配在main函数栈帧上的一个普通int类型再使用detach方法 detach方法会使得创建的线程与main主线程进行分离执行完毕自动回收而join将会阻塞main主线程而main主线程执行完毕时会释放掉main栈帧中的空间因此会把my_i变量释放掉导致类中引用的对象不存在所以输出的结果是一个错误的。 解决方法 第一种将my_i分配到堆区对象就不是一个main栈帧的本地变量这样main栈帧退出时将无法回收到这个变量前提是退出时不进行delete释放。第二种将类中的m_i改成普通成员变量不要传入引用或者指针当一个普通变量当做函数的传入传出参数时是会进行一次拷贝的而引用将不会拷贝。
这里为什么myThreadClass2对象不会因为main栈帧的释放而报错其实就是因为发生了拷贝构造下面进行分析
3.2.2、普通变量的拷贝
为了避免引用带来的问题以及对3.2.1中变量地址引用的一个解答这里使用普通的m_i对象不接收引用为了使得输出效果更好看能够看到执行的流程使用join不使用detach但是依然可以使用detach只是输出不全仅仅是为了输出效果全面
class MyThreadClass3{
public:int m_i;MyThreadClass3(int i): m_i(i) {std::cout 构造函数的执行 std::endl;std::cout m_i变量地址 m_i , i i std::endl;}MyThreadClass3(const MyThreadClass3 myThreadClass3){this-m_i myThreadClass3.m_i;std::cout 拷贝构造函数的执行 std::endl;}virtual ~MyThreadClass3() {std::cout 析构函数的执行 std::endl;}void operator()() {std::cout 子线程开始执行 std::endl;std::cout 1. m_i m_i std::endl;std::cout 2. m_i m_i std::endl;std::cout 3. m_i m_i std::endl;std::cout 4. m_i m_i std::endl;std::cout 5. m_i m_i std::endl;std::cout 子线程执行完毕 std::endl;}
};
int main()
{int i 3;std::cout main i i std::endl;MyThreadClass3 myThreadClass3(i);std::thread mythread3(myThreadClass3);mythread3.join();std::cout main thread executed finish! std::endl;return 0;
}可以很清楚的看到三个变量i和m_i的地址是不一样的也就是说传入参数的时候普通变量会进行拷贝构造而对象也会进行拷贝构造但这里为什么拷贝构造两次就需要深入追源码了这里不做过多的叙述初学入门。但是可以明白的一点就是myThreadClass3对象传入给线程mythread3变量时会被执行拷贝构造
3.3、lambda表达式创建
auto mylambdathread [](){std::cout 子线程开始执行 std::endl;std::cout 子线程执行完毕 std::endl;
};
std::thread mythread4(mylambdathread);
mythread4.detach();4、总结
到此学习完了C最基本的线程的创建与使用
join方法会阻塞执行该代码的线程main中执行这行代码就会阻塞maindetach方法不会。detach方法不会的主要原因是脱离了当前进程所在的会话session成为一个独立的进程该进程只有当前线程执行完毕会被操作系统自动回收资源对于join和detach方法的使用需要根据实际情况判定使用哪个对于数据类型指针或引用、还是普通变量传入时是否会进行拷贝注意会不会因为搭配detach而产生错误。