C++11并发与多线程笔记(2)线程启动、结束、创建线程多法、join、detach

范例演示进程运行的开始和结束

程序运行起来,生成一个进程,该进程所属的主线程开始自动运行;
主线程从main()函数返回,则整个进程执行完毕;
主线程从main()开始执行,那么我们自己创建的线程,也需要从一个函数开始运行(初始函数),一旦这个函数运行完毕,线程也结束运行。
整个进程是否执行完毕的标志是主线程是否执行完,如果主线程执行完毕了,就代表整个进程执行完毕了;此时,如果其它子线程没有执行完毕,那么这些子线程也会被操作系统强行终止。
所以,一般情况下,如果想保持子线程(自己用代码创建的线程)的运行状态的话,要让主线程一直保持运行【这个规律有例外,后面解释】。

  1. 包含thread头文件;
  2. 初始函数
  3. main中开始写代码
    有两个线程在跑,相当于整个程序的执行有两条线在同时走,所以,可以同时干两个事情,即使一条线被堵住了,另外一条线还是可以通行的,这就是多线程。

thread

thread:是个标准库里面的类;

join()

void myPrint() {
	cout << "我的线程开始执行了" << endl;
	cout << "我的线程结束执行了" << endl;

}

thread myobj(myPrint);//myPrint可调用对象,
myobj.join();

创建了线程,线程执行起点(入口)myprint;(2)myPrint线程开始执行。
join()加入/汇合,说白了就是阻塞,阻塞主线程,让主线程等待子线程执行完毕,然后子线程和主线程汇合,然后主线程再往下走。
主线程阻塞到这里等待myprint()执行完,当子线程执行完毕,这个join就执行完毕,主线程就继续往下走。
阻塞主线程并等待myprint子线程执行完

detach()

传统多线程程序,主线程要等待子程序执行完毕,然后自己再最后退出;
detach:分离,也就是主线程不和子线程汇合了,你子线程执行你的,我子线程执行我的,你主线程也不必等我子线程运行结束,你可以先执行结束,这并不影响我子线程的执行。
为什么引入detach():
我们创建了很多子线程,让主线程逐个等待子线程结束,这种编程方法不太好,所以引入了detach();
一旦detach()之后,与这个主线程关联的thread对象就会失去与这个主线程的关联,此时这个子线程就会驻留在后台运行。(主线程跟该子线程失去联系)
这个子线程就相当于被c++运行时库接管,当这个子线程执行完成后,由运行时库负责清理该线程的相关资源(守护线程)。
detach使线程myPrint失去我们的控制。
一旦调用detach,就不能再用join(),否则系统会报告异常。

joinable

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

其它创建线程的方法

用类,以及一个问题范例

class TA {
	public:
		void operator()() {
			cout << "我的线程operator()开始执行" << endl;
			cout << "我的线程operator()结束执行" << endl;
		}
};
TA ta;
thread myobj(ta);
myobj.join();

存在的问题:

class TA {
	public:
		int &i;
		TA(int&i):m_i(i){};
		void operator()() {
			cout << "m_i1的值为:"<<m_i << endl;
			cout << "我的线程operator()结束执行" << endl;
		}
};
int i=6;
TA ta(i);
thread myobj(ta);
myobj.detach();

存在的问题:
一旦detach将主线程和子线程分离后,主线程先结束,那么int i的空间将被释放,那么子线程中的ta的成员变量(虽然将ta拷贝了一次,但是这里是浅拷贝,任然用了i的内存)可能就不是6了。所以存在指针或者引用传递的时候,尽量不要用detach();
大家可能还有一个疑问,一旦调用了detach(),那我主线程执行结束了,我这里用的ta这个对象还在吗?(对象不在了)
这个对象实际上是被复制到线程中去,执行完主线程后,ta会被销毁,但是所复制的ta对象依旧存在。
所以,只要你这个ta对象没有引用,没有指针,那么就不会产生问题;

用lambda表达式

	auto mylambda = []() {
		cout << "我的线程3开始执行了" << endl;
		cout << "我的线程3结束了" << endl;

	};
	thread myobj(mylambda);
	myobj.detach();

线程传参详解,detach()大坑,成员函数做线程函数

传递临时对象作为线程参数

	//一:传递临时对象作为线程参数
	void myprint(const int i, const string &pmybuf) {//查了i和mvar地址,发现并不一样,所以i并不是mvar的引用,实际是值传递,那么我们认为,即便主线程detach了子线程,那么子线程中也是安全的。
		cout << pmybuf << endl;//指针在detach()子线程时,绝对会有问题,所以不能传递指针。
	}
	
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test!";
	thread myobj(myPrint, mvar, mybuf);//但是mybuf是什么时候在将mybuf转为string
								//实际上存在,mybuf都被回收了,(main函数执行完了),系统才用mybuf转string;
	myobj.join();//这时候只能用join,用detach()存在风险

要避免的陷阱(解释1)

	//thread myobj(myPrint, mvar, string(mybuf));//我们这里直接将mybuff转成string对象,这是一个可以保证线程稳定的写法
	//myobj.detach();

要避免的陷阱(解释2)

	class A{
	public:
		A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]"<< endl; }
		A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A& a)拷贝构造函数执行]"  << endl;}			
		~A() { cout << "[A::~A()析构函数执行]" << endl; }
		 int m_i;
	};



	int mvar = 1;
	int mysecondpar = 12;
	thread myobj(myprint, mvar, A(mysecondpar));//我们希望mysecondpar转成A类型对象传递给myprint的第二个参数
												//在创建线程的同时构造临时对象的方法传递参数是可以的(也就是说传递参数的时候一定要用到拷贝构造函数)
												// 只要用临时构造的A类对象作为参数传递给线程,那么就一定能够在主线程执行完毕前把线程函数的第二个参数构造出来
	myobj.join();
	myobj.detach();

总结

  1. 若传递int这种简单的类型参数,建议都是值传递,不要用引用,防止节外生枝。
  2. 如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里用引用来接;
  3. 建议不使用detach(),只使用join(),这样就不存在局部变量失效导致线程对内存非法引用的问题。
  4. 本质是在主线程中,所有待传递给子线程的参数,都会在主线程中复制一份送给子线程(所以类A在主线程中用到了拷贝构造函数)。

传递临时对象作为线程参数继续讲

线程id概念

id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的这个数字不同;也就是说,不同的线程,它的线程id必然是不同的;
调用函数:std::this_thread::get_id()来获得线程id

临时对象构造实际抓捕

class A
{
public:
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]"<<this<<"thread_id="<<std::this_thread::get_id () << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A& a)拷贝构造函数执行]" << this << "thread_id=" << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "thread_id=" << std::this_thread::get_id()<< endl; }
};
void myprint2(  A& pmybuf) {
	cout <<"子线程myprint2的参数地址是:"<< & pmybuf<<"thread_id="<<std::this_thread::get_id() << endl;
}


	cout << "主线程id:" << std::this_thread::get_id() << endl;
	int mvar = 1;
	int mysecondpar = 12;
	//thread myobj(myprint, mysecondpar);//这里拷贝构造函数在子线程中
	thread myobj(myprint2, A(mysecondpar));//而加上隐式类型转换后,拷贝构造就出现在主线程中了
	myobj.join();

本质是在主线程中,所有待传递给子线程的参数,都会在主线程中复制一份送给子线程(所以类A在主线程中用到了拷贝构造函数)。

传递类对象、智能指针作为线程参数

传递类对象

std::ref

	class A{
	public:
		A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]"<<this<<"thread_id="									<<std::this_thread::get_id () << endl; }
		A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A& a)拷贝构造函数执行]" << this << "thread_id=" << std::this_thread::get_id() << endl; }
		~A() { cout << "[A::~A()析构函数执行]" << this << "thread_id=" << 	std::this_thread::get_id()<< endl; }
	};

	void myprint2(  A& pmybuf) {
	pmybuf.m_i = 199;//这时候在子线程修改类A的成员变量,就会影响到主线程的a对象了
	cout <<"子线程myprint2的参数地址是:"<< & pmybuf<<"thread_id="<<std::this_thread::get_id() << endl;
	}

	A a(10);
	thread myobj(myprint3, std::ref(a));//将类对象作为线程参数引用传递到子线程中去,&a等价于std::ref
	//myobj.detach();//存在引用传递,这时候,就不能用detach()了。
	myobj.join();

智能指针

	void myprint3(  unique_ptr<int> pzn) {
		cout<<"thread_id="<<std::this_thread::get_id() << endl;
	}

	unique_ptr<int> myp(new int(100));//智能指针
	thread myobj(myprint3, std::move(myp));//不加std::move会报错,原因未知
	myobj.join();//不可以用detach

用成员函数指针做线程参数

class A
{
public:
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]"<<this<<"thread_id="<<std::this_thread::get_id () << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A& a)拷贝构造函数执行]" << this << "thread_id=" << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "thread_id=" << std::this_thread::get_id()<< endl; }
	 int m_i;
	 void thread_work(int num) {
		 cout << "子线程thread_work执行" << this << "thread_id=" << std::this_thread::get_id() << endl;
	 }
	 void operator()(int num) {//对类对象作为函数参数补充一点使用情形
		 cout << "子线程operator()执行" << this << "thread_id=" << std::this_thread::get_id() << endl;
	 }
};

	A a(10);
	thread myobj(&A::thread_work, &a, 15);//&a等价于std::ref
	myobj.join();
	//A a(10);
	//thread myobj( std::ref(a), 15);//&a等价于std::ref 不用调用拷贝构造函数了,那么后续如果调用myobj.detach()就不安全了。
	//myobj.join();
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值