C++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach
Posted 森明帮大于黑虎帮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach相关的知识,希望对你有一定的参考价值。
2、线程启动、结束、创建线程的多个方法:join、detach
2.1 范例演示线程运行的开始和结束
- 程序运行起来,生成一个进程,该进程所属的主线程开始自动运行。
std::cout<<"C++"<<std::endl;
- 实际上是这个主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
- 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行。
- 整个进程是否执行完毕的标志是 主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了。
- 此时,一般情况下,如果其他子线程还没有执行完毕,那么这些子线程也会被操作系统强行终止。
- 所以,一般情况下,得到一个结论:如果大家想保持子线程(自己用代码创建的线程)运行状态的话,主线程不能停止运行。
2.1.1 thread
std::thread obj(MyPrint); //创建了线程,线程入口函数MyPrint(),然后MyPrint()函数开始执行。
-
创建线程主要包含以下几步:
-
包含一个头文件thread。
-
线程入口初始函数要写。
-
入口初始函数是一个可以调用的对象。一组可以执行的语句称为可调用对象,C++中可调用对象可以是函数、函数指针、lambda表达式、bind绑定对象、还可以是重载了()的类对象( operator() )。
-
必须要明白:有两个线程在跑,相当于整个程序中有两条线在同时走,即使一条被阻塞,另一条也能运行。
-
thread是一个标准库的类。
2.1.2 join()加入/汇合
obj.join(); //阻止主线程执行,而是等待子线程执行完毕join()才算执行完毕,然后才执行主线程。
-
就是阻塞,阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,在执行主线程代码。**
-
一个良好的线程程序,应该是主线程等待子线程执行完毕之后,主线程才退出来。
2.1.3 detach()分离
obj.detach(); //主线程与子线程分离开来,就是主线程自己执行自己的主线程代码,子线程执行自己的子线程代码,主线程不必等待子线程运行结束。
-
detach():主线程与子线程分离开来,就是主线程自己执行自己的主线程代码,子线程执行自己的子线程代码,主线程不必等待子线程运行结束。
-
为什么引入detach():假如创建了许多子线程,让主线程逐个等待子线程结束,一般这种情况是不友好的,所以引入了detach()。一旦detach() 之后,与这个主线程关联的thread对象就会失去与主线程之间的关联,此时的子线程就会驻留在后台运行(主线程与子线程失去联系)。
-
这个子线程就相当于被C++运行时库接管,当这个子线程执行完成后,就会由C++运行时库负责清理该线程相关的资源。(守护进程)
-
detach()使线程入口函数(子线程)失去对于自己的控制。
2.1.4 joinable()
- joinable()判断是否可以成功使用join()或者detach()的,返回true或者false。true表示可以使用join()或者detach(),false表示不可以使用join或者detach()。
- 一旦调用了join()或者detach(),后续就不能在调用了。
2.1.5 代码如下
#include<iostream>
#include<thread>
void MyPrint()
std::cout<<"子线程开始--->"<<std::endl;
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<<"子线程结束7--->"<<std::endl;
std::cout<<"子线程结束8--->"<<std::endl;
std::cout<<"子线程结束9--->"<<std::endl;
std::cout<<"子线程结束10--->"<<std::endl;
int main()
std::thread obj(MyPrint);
//obj.join();
// if(obj.joinable())
// std::cout<<"1 true:joinable()"<<std::endl;
// else
// std::cout<<"1 false:joinable()"<<std::endl;
//
// obj.detach();
// if (obj.joinable())
// std::cout << "2 true:joinable()" << std::endl;
//
// else
// std::cout << "2 false:joinable()" << std::endl;
//
if(obj.joinable())
obj.detach();
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;
return 0;
输出结果:
主线程结束1
主线程结束2
主线程结束3
主线程结束4
子线程开始--->
子线程结束1--->
子线程结束2--->
子线程结束3--->
子线程结束4--->
子线程结束5--->
子线程结束6--->
子线程结束7--->
子线程结束8--->
子线程结束9--->
子线程结束10--->
主线程结束5
主线程结束6
PS C:\\Users\\Administrator\\Desktop\\thread\\build\\thread\\Debug>
2.2 其他创建线程的手法
入口初始函数是一个可以调用的对象。一组可以执行的语句称为可调用对象,C++中可调用对象可以是函数、函数指针、lambda表达式、bind绑定对象、还可以是重载了opetator()成员函数的类对象( operator() )。
2.2.1 用类成员函数operator()、以及一个问题范例创建线程
- 创建一个类,并写成员函数重载(),operator(),实例化化一个该类的对象,把该对象作为线程入口地址。
#include<iostream>
#include<thread>
class A
public:
void operator()()
std::cout<<"子线程开始1"<<std::endl;
std::cout<<"子线程结束1"<<std::endl;
;
int main()
A a;
std::thread obj(a);
obj.join();
std::cout<<"主线程结束"<<std::endl;
return 0;
输出结果:
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug> .\\main.exe
子线程开始1
子线程结束1
主线程结束
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
- 另一种实例:当类中有成员变量时要小心一下情况:
#include<iostream>
#include<thread>
template<class T>
class A
public:
A(T& a)
:a_(a)
~A()
void operator()()
std::cout<<"子线程开始1"<<std::endl;
std::cout<<"子线程结束1"<<std::endl;
std::cout<<"a_1的值:"<<a_<<std::endl;
std::cout<<"a_2的值:"<<a_<<std::endl;
std::cout<<"a_3的值:"<<a_<<std::endl;
std::cout<<"a_4的值:"<<a_<<std::endl;
std::cout<<"a_5的值:"<<a_<<std::endl;
private:
T a_;
;
int main()
//A a;
int b=10;
A<int> a(b);
std::thread obj(a);
//obj.join();
obj.detach();
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<<"主线程结束7"<<std::endl;
std::cout<<"主线程结束8"<<std::endl;
std::cout<<"主线程结束9"<<std::endl;
std::cout<<"主线程结束10"<<std::endl;
return 0;
这时会发现输出结果会有不一样的结果:
- 输出结果1:
主线程结束2
主线程结束3
主线程结束4
主线程结束5
主线程结束6
主线程结束7
主线程结束8
主线程结束9
主线程结束10子线程开始1
子线程结束1
a_1的值:
主线程结束1
主线程结束2
主线程结束3
主线程结束4
主线程结束5
主线程结束6
主线程结束7
主线程结束8
主线程结束9
主线程结束10
子线程开始1
- 输出结果2:
主线程结束1
主线程结束2
主线程结束3
主线程结束4
主线程结束5
主线程结束6子线程开始1
子线程结束1
a_1的值:
主线程结束7
主线程结束810
a_2的值:10
a_3的值:10
a_4的值:10
a_5的值:10
主线程结束9
主线程结束10
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
- 原因如下:
- **main函数中变量b是一个局部变量,存储在栈区。**当使用detach()函数而不是join()函数的时候就会出现问题,如果子线程先于主线程先结束的话没有问题。但是当主线程先结束,子线程后结束的话:那么主线程中栈区变量自动销毁,变量b传递不过去,而子线程又是引用传递过去,两个值一样那么子线程就接受不到传过来的值,那么子线程就不能输出成员变量的值。
- 解决方法:
- 使用join()而不是detach()函数。
- 类中不是引用来接受传递过去的值,而是普通接受,那么就是拷贝一份值到类中就可以。
- 解决方法:
- **main函数中变量b是一个局部变量,存储在栈区。**当使用detach()函数而不是join()函数的时候就会出现问题,如果子线程先于主线程先结束的话没有问题。但是当主线程先结束,子线程后结束的话:那么主线程中栈区变量自动销毁,变量b传递不过去,而子线程又是引用传递过去,两个值一样那么子线程就接受不到传过来的值,那么子线程就不能输出成员变量的值。
#include<iostream>
#include<thread>
template<class T>
class A
public:
A(T a)
:a_(a)
~A()
void operator()()
std::cout<<"子线程开始1"<<std::endl;
std::cout<<"子线程结束1"<<std::endl;
std::cout<<"a_1的值:"<<a_<<std::endl;
std::cout<<"a_2的值:"<<a_<<std::endl;
std::cout<<"a_3的值:"<<a_<<std::endl;
std::cout<<"a_4的值:"<<a_<<std::endl;
std::cout<<"a_5的值:"<<a_<<std::endl;
private:
T a_;
;
int main()
//A a;
int b=10;
A<int> a(b);
std::thread obj(a);
//obj.join();
obj.detach();
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<<"主线程结束7"<<std::endl;
std::cout<<"主线程结束8"<<std::endl;
std::cout<<"主线程结束9"<<std::endl;
std::cout<<"主线程结束10"<<std::endl;
return 0;
输出结果:
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug> .\\main.exe
主线程结束1
主线程结束2
主线程结束3
子线程开始1主线程结束4
主线程结束5
主线程结束6
主线程结束7
子线程结束1
a_1的值:
主线程结束810
a_2的值:10
a_3的值:10
a_4的值:10
a_5的值:10
主线程结束9
主线程结束10
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
-
其实可能还有一个疑问,对象a也是一个局部对象,当主线程main函数先于子线程结束的时候,那么对象也被销毁了,那为啥上面代码后面也还能执行后续代码呢?
- 原因:当主线程先于子线程先结束的时候,对象a确实被销毁了。但是使用std:: thread obj(a)是把a对象复制了一份到线程中去了,所以能执行后续代码。所以只要这个对象不是以引用传递或者以指针形式传递就没有问题。
- 代码演示:
#include<iostream> #include<thread> template<class T> class A public: A(T a) :a_(a) std::cout<<"A的构造函数"<<std::endl; A(const A& A1) :a_(A1.a_) std::cout<<"A1的拷贝构造"<<std::endl; ~A() std::cout<<"A的析构函数"<<std::endl; void operator()() std::cout<<"子线程开始1"<<std::endl; std::cout<<"子线程结束1"<<std::endl; std::cout<<"a_1的值:"<<a_<<std::endl; std::cout<<"a_2的值:"<<a_<<std::endl; std::cout<<"a_3的值:"<<a_<<std::endl; std::cout<<"a_4的值:"<<a_<<std::endl; std::cout<<"a_5的值:"<<a_<<std::endl; private: T a_; ; int main() //A a; int b=10; A<int> a(b); std::thread obj(a); //obj.join(); obj.detach(); 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<<"主线程结束7"<<std::endl; std::cout<<"主线程结束8"<<std::endl; std::cout<<"主线程结束9"<<std::endl; std::cout<<"主线程结束10"<<std::endl; return 0;
输出结果:
A的构造函数 A1的拷贝构造 主线程结束1 主线程结束2 主线程结束3 主线程结束4 主线程结束5 主线程结束6 主线程结束7 主线程结束8 主线程结束9 主线程结束10子线程开始1 子线程结束1 a_1的值: A的析构函数 PS D:\\C++11多线程代码编写\\build\\thread线程创建方法2\\Debug>
2.2.2 用lambda表达式
#include<iostream>
#include<thread>
int main()
auto lambadThread=[]()
std::cout<<"子线程开始1"<<std::endl;
std::cout<<"子线程结束1"<<std::endl;
std::cout<<"子线程结束2"<<std::endl;
std::cout<<"子线程结束3"<<std::endl;
std::cout<<"子线程结束4"<<std::endl;
;
std::thread obj(lambadThread);
if(obj.joinable())
obj.join();
std::cout<<"主线程结束1"<<std::endl;
return 0;
输出结果:
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法3\\Debug> .\\main.exe
子线程开始1
子线程结束1
子线程结束2
子线程结束3
子线程结束4
主线程结束1
PS D:\\C++11多线程代码编写\\build\\thread线程创建方法3\\Debug>
以上是关于C++11多线程第二篇:线程启动结束创建线程的多个方法:joindetach的主要内容,如果未能解决你的问题,请参考以下文章
iOS开发:深入理解GCD 第二篇(dispatch_groupdispatch_barrier基于线程安全的多读单写)