一、范例演示线程运行的开始和结束
visual studio 2015环境
以下代码示例中:不会有system("pasue");因为这会让程序运行卡住
看不出效果,要解决控制台闪退,最好别用这种方法。
可以右键项目属性--->链接器--->系统在子系统中选择
控制台 (/SUBSYSTEM:CONSOLE),最后点击应用确定。
还有一点需要注意!或许你按F5运行起来,依旧会闪退,这是由于F5是
Debugging模式,在这个模式下,当程序运行结束后,窗口不会继续
保持打开状态。
而Ctrl+F5是Start Without Debugging模式,
在这个模式下,就可以看到运行结果了。
可以先了解一下C++的可调用对象(未来会写篇博客补充这一部分内容,留坑)
在thread的初始化中需要传入可调用对象来作为线程的入口函数
可调用对象可以作为回调函数使用
回调函数就是一个被作为参数传递的函数
- 程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
- 例如:在main()函数中,cout << "Hello China!" << endl; 实际上这个语句是由主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
- 主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束
- 整个进程是否执行完毕的标志是:主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了; 此时,一般情况下,如果其他子线程还没有执行完毕,那么这些子线程也会被操作系统强行终止。
- 所以,一般情况下,我们得到一个结论:如果想保持子线程(自己用代码创建的线程)的运行状态的话,那么就要让主线程一直保持运行,不要让主线程运行完毕;
- 但是以上规律有例外(主线程可以不等待子线程执行完,自己先结束),后续会再次提出!!!
1. join
(1)首先包含一个头文件 thread
#include <thread>
using namespace std;
(2)函数(初始函数)要写
//自己创建的线程也要从一个函数(初始函数)开始运行:
void myCout()
{
cout << "我的线程开始执行了" << endl;
/*....
*....
*....
*/
cout << "我的线程执行完毕了" << endl;
}
(3)main中开始写代码
//创建线程
thread myObj(myCout);//生成thread 对象,传入myCout参数(函数可做参数)
myObj.join();//调用方法 join 等待子线程执行完
cout << "主线程收尾,最终主线程安全正常退出!" << endl;
return 0;
(4)完整代码+运行结果
// projectAll.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//自己创建的线程也要从一个函数(初始函数)开始运行:
void myCout()
{
cout << "我的线程开始执行了" << endl;
/*....
*....
*....
*/
cout << "我的线程执行完毕了" << endl;
}
int main()
{
//一、范例演示线程运行的开始和结束
//程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
//实际上这个是由主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
//cout << "Hello China!" << endl;
//主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束
//创建线程
thread myObj(myCout);//生成thread 对象,传入myCout参数(函数可做参数)
myObj.join();//调用方法 join 等待子线程执行完
cout << "主线程收尾,最终主线程安全正常退出!" << endl;
return 0;
}
2.detach
detach():传统多线程程序,主线程要等待子线程程序执行完毕,然后自己再最后退出;
join:结合
detach:分离 ,也就是主线程不和子线程汇合了,主线程执行主线程的,
子线程自己执行子线程的,主线程可以不必等待子线程运行结束,它可以先
执行结束,这并不会影响子线程的执行。
- 为什么引入detach():我们创建了很多子线程,让主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach()[但是传统的等待我认为比较安全稳定]
- 一旦引入detach()之后,与这个主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行(主线程跟该子线程失去联系);这个子线程就相当于被C++运行时库(系统的东西)接管了,当这个子线程执行完成后,由运行时库复杂清理该线程相关的资源(驻留后台的被称为:守护线程)
(1)代码测试:+ 运行结果图
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//自己创建的线程也要从一个函数(初始函数)开始运行:
void myCout()
{
cout << "我的线程开始执行了" << endl;
/*....
*....
*....
*/
//多打印几行拖一下时间,看一下detach的运行效果
cout << "我的线程执行完毕了1" << endl;
cout << "我的线程执行完毕了2" << endl;
cout << "我的线程执行完毕了3" << endl;
cout << "我的线程执行完毕了4" << endl;
cout << "我的线程执行完毕了5" << endl;
cout << "我的线程执行完毕了6" << endl;
cout << "我的线程执行完毕了7" << endl;
cout << "我的线程执行完毕了8" << endl;
cout << "我的线程执行完毕了9" << endl;
cout << "我的线程执行完毕了10" << endl;
cout << "我的线程执行完毕了11" << endl;
cout << "我的线程执行完毕了12" << endl;
cout << "我的线程执行完毕了13" << endl;
cout << "我的线程执行完毕了14" << endl;
cout << "我的线程执行完毕了15" << endl;
}
int main()
{
//一、范例演示线程运行的开始和结束
//程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
//实际上这个是由主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
//cout << "Hello China!" << endl;
//主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束
//创建线程
thread myObj(myCout);//生成thread 对象,传入myCout参数(函数可做参数)
//myObj.join();//调用方法 join 等待子线程执行完
myObj.detach();
cout << "主线程收尾,最终主线程安全正常退出!" << endl;
//进程退出表示主线程执行完了,主线程执行完,进程就退出了
//system("pause");
return 0;
}
detach():可以看出当子线程的运行时间一长,不论子线程是否结束,主线程可以不等待先结束,进程退出
(2)修改代码再次测试 + 运行结果图
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//自己创建的线程也要从一个函数(初始函数)开始运行:
void myCout()
{
cout << "我的线程开始执行了" << endl;
/*....
*....
*....
*/
//多打印几行拖一下时间,看一下detach的运行效果
cout << "我的线程执行完毕了1" << endl;
cout << "我的线程执行完毕了2" << endl;
cout << "我的线程执行完毕了3" << endl;
cout << "我的线程执行完毕了4" << endl;
cout << "我的线程执行完毕了5" << endl;
cout << "我的线程执行完毕了6" << endl;
cout << "我的线程执行完毕了7" << endl;
cout << "我的线程执行完毕了8" << endl;
cout << "我的线程执行完毕了9" << endl;
cout << "我的线程执行完毕了10" << endl;
cout << "我的线程执行完毕了11" << endl;
}
int main()
{
//一、范例演示线程运行的开始和结束
//程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
//实际上这个是由主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
//cout << "Hello China!" << endl;
//主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束
//创建线程
thread myObj(myCout);//生成thread 对象,传入myCout参数(函数可做参数)
//myObj.join();//调用方法 join 等待子线程执行完
myObj.detach();
cout << "hello 主线程1" << endl;
cout << "hello 主线程2" << endl;
cout << "hello 主线程3" << endl;
cout << "hello 主线程4" << endl;
cout << "hello 主线程5" << endl;
cout << "hello 主线程6" << endl;
//进程退出表示主线程执行完了,主线程执行完,进程就退出了
//system("pause");
return 0;
}
可以多跑跑几次看看效果,每一次都不太一样
现在用的是detach();主线程走主线程的,子线程执行子线程的,两条路
各执行各的,互不影响。
可以发现主线程执行完之后,进程就退出了,导致后面子线程的输出就输出
不到屏幕上了(子线程被C++运行时库接管了),子线程转入后台执行了。
detach()使子线程myCout失去我们自己的控制。
注意!一旦调用了detach(),子线程就到后台了,失控了,那就不能再
调用join了,否则系统会报告异常。如下图所示:
3.joinable():判断是否可以成功使用join()或者detach()
joinable():返回true(可以join或者detach)或者false(不能join不能detach)
(1)代码
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//自己创建的线程也要从一个函数(初始函数)开始运行:
void myCout()
{
cout << "我的线程开始执行了" << endl;
/*....
*....
*....
*/
//多打印几行拖一下时间,看一下detach的运行效果
cout << "我的线程执行完毕了1" << endl;
cout << "我的线程执行完毕了2" << endl;
cout << "我的线程执行完毕了3" << endl;
cout << "我的线程执行完毕了4" << endl;
cout << "我的线程执行完毕了5" << endl;
cout << "我的线程执行完毕了6" << endl;
cout << "我的线程执行完毕了7" << endl;
cout << "我的线程执行完毕了8" << endl;
cout << "我的线程执行完毕了9" << endl;
cout << "我的线程执行完毕了10" << endl;
cout << "我的线程执行完毕了11" << endl;
}
int main()
{
//一、范例演示线程运行的开始和结束
//程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
//实际上这个是由主线程在执行,主线程从main()函数返回,则整个进程执行完毕。
//cout << "Hello China!" << endl;
//主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表着我们这个线程运行结束
//创建线程
thread myObj(myCout);//生成thread 对象,传入myCout参数(函数可做参数)
if (myObj.joinable())
{
cout << "1:joinable() == true" << endl;
}
else
{
cout << "1:joinable() == false" << endl;
}
//myObj.join();//调用方法 join 等待子线程执行完
myObj.detach();
if (myObj.joinable())
{
cout << "2:joinable() == true" << endl;
}
else
{
cout << "2:joinable() == false" << endl;
}
//myObj.join();
cout << "hello 主线程1" << endl;
cout << "hello 主线程2" << endl;
cout << "hello 主线程3" << endl;
cout << "hello 主线程4" << endl;
cout << "hello 主线程5" << endl;
cout << "hello 主线程6" << endl;
//进程退出表示主线程执行完了,主线程执行完,进程就退出了
//system("pause");
return 0;
}
(2)结果示意图
(3)结论
调用join后不可以detach或者join,同样调用detach后也不可以在调用detach或者join
二、其他创建线程的手法
1、类作为调用对象
仿函数(可调用对象):通过在类内定义运算符重载函数,用类实现函数调用。 通过在类内部重载(),用类模拟函数的调用。
(1)join() 代码测试 + 运行结果图
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//类中只要包含operator() ()就变成可调用对象了
//这种方式称为仿函数
//通过在类内定义运算符重载函数,用类实现函数调用。
//通过在类内部重载(),用类模拟函数的调用。
class MyClass
{
//那么我们用类对象创建线程的话,线程入口点就是operator() ()
public:
//重载()
//变成可调用对象
void operator() ()//不带参数
{
cout << "我的线程operator()开始执行了" << endl;
//.......
cout << "我的线程operator()结束执行了" << endl;
}
MyClass();
~MyClass();
private:
};
MyClass::MyClass()
{
}
MyClass::~MyClass()
{
}
int main()
{
//二、其他创建线程的手法
//(1)用类对象(可调用对象),以及一个问题范例
MyClass my;//类对象
thread myObjClass(my);//my 可调用对象
myObjClass.join();//等待子线程执行结束
cout << "hello 主线程1" << endl;
return 0;
}
(2)detach() 代码测试 + 运行结果图
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//自己创建的线程也要从一个函数(初始函数)开始运行:
void myCout()
{
cout << "我的线程开始执行了" << endl;
/*....
*....
*....
*/
//多打印几行拖一下时间,看一下detach的运行效果
cout << "我的线程执行完毕了1" << endl;
cout << "我的线程执行完毕了2" << endl;
cout << "我的线程执行完毕了3" << endl;
cout << "我的线程执行完毕了4" << endl;
cout << "我的线程执行完毕了5" << endl;
cout << "我的线程执行完毕了6" << endl;
cout << "我的线程执行完毕了7" << endl;
cout << "我的线程执行完毕了8" << endl;
cout << "我的线程执行完毕了9" << endl;
cout << "我的线程执行完毕了10" << endl;
cout << "我的线程执行完毕了11" << endl;
}
//类中只要包含operator() ()就变成可调用对象了
//这种方式称为仿函数
//通过在类内定义运算符重载函数,用类实现函数调用。
//通过在类内部重载(),用类模拟函数的调用。
class MyClass
{
//那么我们用类对象创建线程的话,线程入口点就是operator() ()
public:
int &m_i;//m_i成员变量 是引用
//int m_i; //或者不用引用这样就是把值拷贝过来了,这就没有问题了
//给成员变量赋值
MyClass(int &i):m_i(i) {}
//重载()
//变成可调用对象
void operator() ()//不带参数
{
//cout << "我的线程operator()开始执行了" << endl;
////.......
//cout << "我的线程operator()结束执行了" << endl;
cout << "m_i1的值为:" << m_i << endl;//会产生不可预料的后果
cout << "m_i2的值为:" << m_i << endl;
cout << "m_i3的值为:" << m_i << endl;
cout << "m_i4的值为:" << m_i << endl;
cout << "m_i5的值为:" << m_i << endl;
cout << "m_i6的值为:" << m_i << endl;
}
private:
};
int main()
{
//二、其他创建线程的手法
//(1)用类对象(可调用对象),以及一个问题范例
int myi = 6;
MyClass my(myi);//类对象
thread myObjClass(my);//my 可调用对象
//myObjClass.join();//等待子线程执行结束
myObjClass.detach();
cout << "hello 主线程1" << endl;
return 0;
}
存在坑点:detach 在这里主线程执行主线程的,子线程执行子线程的 若是主线程先执行完,子线程后执行完, 在这个例子中,子线程中执行的是打印m_i(是一个引用) 理解一下,在这里就是myi的别名 m_i --- i --- myi 而myi是主线程的局部变量,当主线程执行结束后 myi的内存就已经被回收了或者说销毁了 而子线程未执行完,还在打印这段内存(被回收的内存)的内容,这会产生不可预料的后果
//若是把引用去掉
int m_i; //这样就是把值拷贝过来了,这就没有问题了
(3)仍有疑问?
一旦调用了detach(),那我主线程执行结束了,这里的my类对象是否还在?
肯定不存在了(局部变量),但是不影响。
因为这个my对象【thread myObjClass(my)】实际上被复制到线程中了;
执行完主线程后,my会被销毁,但是所复制的MyClass对象依旧存在。
所以,只有此类对象里没有引用,没有指针,就不会产生问题;
如何证明这一过程中my是被复制了?(自己写一个拷贝构造函数)
一般来说,假如程序员没有自行编写复制构造函数,那么编译器会自动地替每一个类创建一个复制构造函数;相反地,程序员有自行编写复制构造函数,那么编译器就不会创建它。
<1> 代码
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
//类中只要包含operator() ()就变成可调用对象了
//这种方式称为仿函数
//通过在类内定义运算符重载函数,用类实现函数调用。
//通过在类内部重载(),用类模拟函数的调用。
class MyClass
{
//那么我们用类对象创建线程的话,线程入口点就是operator() ()
public:
int &m_i;//m_i成员变量 是引用
//int m_i; //或者不用引用这样就是把值拷贝过来了,这就没有问题了
//给成员变量赋值
MyClass(int &i):m_i(i)
{
cout << "MyClass()构造函数被执行" << endl;
}
//拷贝构造函数
MyClass(const MyClass &my) :m_i(my.m_i)
{
cout << "MyClass()拷贝构造函数被执行" << endl;
}
~MyClass()
{
cout << "~MyClass()析构函数被执行" << endl;
}
//重载()
//变成可调用对象
void operator() ()//不带参数
{
//cout << "我的线程operator()开始执行了" << endl;
////.......
//cout << "我的线程operator()结束执行了" << endl;
cout << "m_i1的值为:" << m_i << endl;//会产生不可预料的后果
cout << "m_i2的值为:" << m_i << endl;
cout << "m_i3的值为:" << m_i << endl;
cout << "m_i4的值为:" << m_i << endl;
cout << "m_i5的值为:" << m_i << endl;
cout << "m_i6的值为:" << m_i << endl;
}
private:
};
int main()
{
//二、其他创建线程的手法
//(1)用类对象(可调用对象),以及一个问题范例
int myi = 6;
MyClass my(myi);//类对象
thread myObjClass(my);//my 可调用对象
//myObjClass.join();//等待子线程执行结束
myObjClass.detach();
cout << "hello 主线程1" << endl;
return 0;
}
<2>运行结果图
- main中my这个对象是结束了
<3>结论
- 说明这一过程中确实my类对象是被复制到了线程中。
- 而析构函数执行是,在main中的my对象被析构了;复制的那一个到后台看不见。
- 对比一下join()的运行结果示意图就很明显:
2.用lambda表达式作为调用对象
lambda表达式:[capture] (parameters) mutable ->return-type{statement}
(1)代码,调用 join()
// projectAll.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <iostream>
#include <stdio.h>
#include <string>
#include <map>
#include <thread>
using namespace std;
int main()
{
//用lambda 表达式
auto myLambdaThread = [](){
cout << "我的线程lambda开始执行了" << endl;
//.....
cout << "我的线程lambda执行结束了" << endl;
};
thread myObjLambda(myLambdaThread);
myObjLambda.join();
cout << "hello 主线程1" << endl;
return 0;
}