线程启动、结束,创建线程的多方法

275 阅读6分钟

一、范例演示线程运行的开始和结束

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;
}

(2)运行结果图

在这里插入图片描述