C++11并发与多线程(2)-单个子线程创建

线程启动、结束,创建线程方法,join,detach

1、范例演示:线程的开始和结束

线程开始、结束已经保持运行的规则:

1 、程序运行起来,生成一个进程,该进程所属的主线程开始自动运行,主线程从main()函数开始,main()函数返回,则整个进程执行完毕;
2、主线程从main()函数开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,就代表我们这个线程运行结束);
3、整个进程执行完毕的标志为:主线程执行完毕,一旦主线程执行完毕代表整个进程执行完毕;而如果其他依赖这个主线程的子线程还没有执行完毕,则会被操作系统强行终止;
4、一般情况下,如果大家想保持子线程运行状态的话,要让其依赖的主线程一直保持运行。

注意凡事均有例外,第3、4条也有其他情况,也就是后续所讲的detach()函数

线程的创建步骤:

1、包含thread头文件;
2、子线程开始的函数存在(自己编写);
3、main函数创建线程,一般是使用thread类创建对象即可,这时线程就已经运行了。
4、一般情况下,子线程开始就要进行如下两种情况:一是使用join()阻塞主线程,等待子线程执行结束,再继续执行主线程;二是使用detach()使得子线程与主线程分离,主线程不必等子线程,不影响子线程的执行。

示例:创建子线程,join()函数阻塞主线程

#include <iostream>
#include <thread>
using namespace std;

// 线程执行的入口函数
void myPrint()
{
	cout << "我的线程开始运行" << endl;
	//-------------
	//-------------
	cout << "我的线程运行完毕" << endl;
	return;
}

int main(int argc, char* argv[])
{
	// (1)使用thread类创建了一个子线程,线程执行入口函数是myPrint
	// (2)创建线程的同时,子线程就开始执行了
	thread myThread(myPrint);

	// (3) join()阻塞主线程, 等待myPrint执行完,当myPrint执行完毕,join()就执行完毕,主线程继续往下执行
	myThread.join();

	cout << "Hello World!" << endl;
	return 0;
}

调试步骤:

在 join() 设置断点可看到主线程等待子线程的过程
F11逐语句,就是每次执行一行语句,如果碰到函数调用,它就会进入到函数里面
F10逐过程,碰到函数时,不进入函数,把函数调用当成一条语句执行

示例:创建子线程,detach()函数分离子线程与主线程

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
	cout << "我的线程开始运行" << endl;
	//-------------
	//-------------
	cout << "我的线程运行完毕" << endl;
	return;
}

int main(int argc, char* argv[])
{
	thread myThread(myPrint);

	// detach():分离,主线程不再与子线程汇合,不再等待子线程
	// detach()后,子线程和主线程失去关联,驻留在后台,由C++运行时库接管
	// 也就是说,无论主线程存在与否,子线程都会再后台运行,直至其绑定的函数执行结束
	// 为什么引入detach():我们创建了很多子线程,让主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach
	myThread.detach();

	cout << "Hello World!" << endl;
	return 0;
}

总结
1、使用thread类创建一个对象时,就有两个线程在跑了,相当于整个程序的执行有两条线在同时走,所以,可以同时干两个事,即使一条线被堵住了,另外一条线也是可以通行的,这就是多线程
2、detach()函数分离了主线程和子线程,主线程和子线程不再汇合,主线程不必等子线程,不影响子线程的执行。一旦detach()之后,与主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行,这个子线程就相当于被C++运行时库接管,当这个子线程执行完成之后,由运行时库负责清理该线程相关的资源(守护线程)。这也导致我们对子线程myprint失去了控制权
3、但是,失去对子线程的控制权不太好,一个书写良好的程序,应该是主线程等待子线程执行完毕后,自己才能最终退出,也就是使用join()阻塞主线程,等待子线程执行结束,再继续执行主线程

2、joinable()

判断是否可以成功使用 join() 或者 detach(); 返回 true(可以用join()或者detach())或者 false (不能使用join()或者detach())

#include <iostream>
#include <thread>
using namespace std;

void myPrint()
{
	cout << "我的线程开始运行" << endl;
	//-------------
	//-------------
	cout << "我的线程运行完毕" << endl;
	return;
}

int main(int argc, char* argv[])
{
	thread myThread(myPrint);
	myThread.join();

	// joinable()判断是否可以成功使用join()或者detach()
	// 如果返回true,证明可以调用join()或者detach()
	// 如果返回false,证明调用过join()或者detach(),join()和detach()都不能再调用了
	if (myThread.joinable())
	{
		cout << "可以调用可以调用join()或者detach()" << endl;
	}
	else
	{
		cout << "不能调用可以调用join()或者detach()" << endl;
	}
	
	cout << "Hello World!" << endl;
	return 0;
}

3、其他创建线程的方式

利用可调用对象来构造子线程,C++中可调用对象包括:函数、函数指针、lambda表达式、bind创建的对象以及仿函数对象。

3.1、仿函数对象


#include <iostream>
#include <thread>
using namespace std;

//创建一个类,并编写圆括号重载函数,初始化一个该类的对象,把该对象作为线程入口地址
class Student {
public:

	Student(){}
	// 圆括号重载
	void operator()() {
		cout << "我的线程开始运行" << endl;
		//-------------
		//-------------
		cout << "我的线程运行完毕" << endl;
	}
};

int main()
{

	// 利用仿函数对象构建子线程
	Student stu;
	thread myThread(stu);
	myThread.join();

	cout << "I Love China" << endl;

	return 0;
}

引入一个bug:对于detach()函数,使用仿函数,在类中以指针、引用等引入主线程的局部变量,那么在主线程结束时,该变量被释放,那么子线程中使用该变量的进行处理就会出现错误。

#include <iostream>
#include <thread>

using namespace std;

class Student {
public:
	Student(int& id):m_Id(id){}

	void operator()() {
		cout << "我的学生1" << m_Id << endl;
		cout << "我的学生2" << m_Id << endl;
		cout << "我的学生3" << m_Id << endl;
		cout << "我的学生4" << m_Id << endl;
		cout << "我的学生5" << m_Id << endl;
	}

private:
	int m_Id;
};

int main()
{
	
	// 利用仿函数对象构建子线程
	int a = 6;
	Student stu(a);
	thread myThread(stu);
	myThread.detach();

	cout << "I Love China" << endl;

	return 0;
}

运行结果:
在这里插入图片描述
可以看出,使用detach()函数,使得在初始化时的参数无法无法正常使用故而,在使用仿函数对象构建线程时,不在该类中使用引用、指针,一般不会出现问题

那么大家可能还有一个疑问:一旦调用了detach(),那主线程执行结束了,这里用的这个stu对象还在吗?

解释:对象不在了,但是,这个对象实际上是被 复制(深拷贝) 到子线程中去;执行完主线程后,stu会被销毁,但是所复制的Student对象依旧存在。所以,只要这个Student对象里没有引用、没有指针,那么就不会产生问题。

#include <iostream>
#include <thread>

using namespace std;

class Student {
public:
	
	Student(int id):m_Id(id){
		cout << "构造函数被执行了" << endl;
	}

	Student(const Student& stu) :m_Id(stu.m_Id) {
		cout << "深拷贝构造函数被执行了" << endl;
	}

	~Student() {
		cout << "析构函数被执行了" << endl;
	}

	void operator()() {
		cout << "我的学生1:" << this->m_Id << endl;
		cout << "我的学生2:" << m_Id << endl;
		cout << "我的学生3:" << m_Id << endl;
		cout << "我的学生4:" << m_Id << endl;
		cout << "我的学生5:" << m_Id << endl;
	}

private:
	int m_Id;
};

int main()
{
	
	// 利用仿函数对象构建子线程
	int a = 6;
	Student stu(a);
	thread myThread(stu);
	myThread.detach();

	cout << "I Love China" << endl;

	return 0;
}

VScode调试结果(这里类对象传入的是整型变量):
在这里插入图片描述
从结果中,可以发现,在构建子线程时,调用了类中的深拷贝构造函数,那么使用detach()时,即使仿函数对象被主线程释放掉,那么子线程依旧可以正常执行。这里没有看到子线程的析构函数,可以把detach()函数换成join()即可看到更加清晰看到子线程的析构函数执行,包括传入的参数变量。如下:
在这里插入图片描述
注意:
1、使用使用join()函数与detach()函数时,释放的类对象实现时间视情况而定;

2、另外,在使用VS2019时,可能是编译器的原因,即使使用整型变量构建类对象stu后,没有使用指针或者引用,依然无法使得子线程中正常使用传入的参数,结果如下图。所以,后续的文章中均使用VScoode进行调试。
在这里插入图片描述

3.2、lambda表达式

#include <iostream>
#include <thread>

using namespace std;

int main()
{

	// lambda表达式 线程的入口
	auto lambdaThread = [] {
		cout << "我的线程开始执行了" << endl;
		//-------------
		//-------------
		cout << "我的线程开始执行了" << endl;
	};

	thread myThread(lambdaThread);
	myThread.join();

	cout << "I Love China" << endl;

	return 0;
}
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值