c++11多线程编程(三)线程传参详解,detach()大坑,成员函数做线程函数

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

1.要避免的陷阱(解释1)

  • i 并不是真正的引用传递,所以是安全的
  • mybuf 是传指针,所以是不安全的,可以将char* pmybuf改为 const string &类型,为复制的,开辟的新内存(暂时感觉没有问题)。
  • 所以不推荐用引用,绝对不可以用指针
#include <thread>
#include <iostream>

using namespace std;

void my_print(const int &i, char * pmybuf)
{
	cout << i << endl;
	cout << pmybuf << endl;
	return;
}

int main()
{
	int mvar = 1;
	int& myary = mvar; //引用是原来地址的别名,所以两个地址是相同的
	char mybuf[] = "this is a test!";
	thread mytobj(my_print, mvar, mybuf); //第一个参数是函数名,后面的为函数参数
	//mytobj.join();
	//假设现在没有join,使用的是detach(),主线程和子线程分开执行
	//但是这个里面的i,不是引用,相当于把mvar复制给i了,其实是一个值
	//所以虽然主线程比子线程先结束,但程序是安全的,但不建议这样使用
	mytobj.detach();
	cout << "Ilove chain !" << endl;

	return 0;
}

2.要避免的陷阱(解释2)

  • 有种可能是:主线程都执行完了,mybuf已经被回收了,此线程才开始,才有mybuf到pbuf的转换。事实上存在mybuf都被回收了,系统采用mybuf转pbuf的现象,就会出错。
#include <thread>
#include <iostream>

using namespace std;

void my_print(const int &i, const string &pmybuf)
{
	cout << i << endl;
	cout << pmybuf.c_str() << endl;
	return;
}

int main()
{
	int mvar = 1;
	int& myary = mvar; 
	char mybuf[] = "this is a test!";
	thread mytobj(my_print, mvar, mybuf); //但是mybuf到底什么时候转换成string
	mytobj.detach();
	cout << "I love chain !" << endl;

	return 0;
}

改为:

#include <thread>
#include <iostream>

using namespace std;


void my_print(const int &i, const string &pmybuf)
{
	cout << i << endl;
	cout << pmybuf.c_str() << endl;
	return;
}

int main()
{
	int mvar = 1;
	int& myary = mvar; 
	char mybuf[] = "this is a test!";
	thread mytobj(my_print, mvar, string(mybuf));  //直接将mybuf转为string生成一个临时的虚拟对象,问题可以解决。 
	mytobj.detach();
	cout << "I love chain !" << endl;

	return 0;
}

验证

#include <thread>
#include <iostream>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int 转换成一个类a对象
	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)拷贝构造函数执行]" << endl; }
	~A() { cout << "[A::A()析构函数执行]" << endl; }
};

void my_print(const int& i, const A &pmybuf)
{
	cout << &pmybuf << endl;
	return;
}

int main()
{
	int mvar = 1;
	int mysecondpar = 12;
	//我们希望 mysecondpar 转成A类型对象传递给myprint线程函数的第二个参数
	//我们希望用mysecondpar构造一个对象,但是验证并没有构建出来,主线程执行完后
	//mysecondpar已经被回收了,导致后面对象根本构造不出来
	thread mytobj(my_print, mvar, mysecondpar);
	mytobj.detach();
	cout << "I love chain !" << endl;
	return 0;
}

尝试构造一个类A的临时对象

#include <thread>
#include <iostream>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int 转换成一个类a对象
	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)拷贝构造函数执行]" << endl; }
	~A() { cout << "[A::A()析构函数执行]" << endl; }
};

void my_print(const int& i, const A &pmybuf)
{
	cout << &pmybuf << endl;
	return;
}

int main()
{
	int mvar = 1;
	int mysecondpar = 12;
	//使用一个临时的对象转换,在创建线程的同时传递一个临时对象是可行的。
	thread mytobj(my_print, mvar, A(mysecondpar));
	mytobj.detach();
	cout << "I love chain !" << endl;
	return 0;
}

3.总结

  • 只要用临时构造函数作为参数的传递给线程,那么就一定能够执行完毕前把第二个参数构造出来,从而确保即便detach(),子线程也能安全运行。
    主要针对 detach()
  • 如传递int类型参数,都是值传递,不要用引用,防止节外生枝
  • 如果传递类对象,不要隐式类型转换。全部都在创建线程这一行就构建出临时对象,然后在函数参数里用引用来接,如果不用引用,否则系统还会多构造一个对象,造成浪费。
  • 终极结论:建议不是用detach(),只是用join()。这样就不存在局部变量失效,线程对内存的非法使用。

二、临时对象作为线程参数继续讲

1.线程id概念

  • 每个线程不管是主线程还是子线程,每个线程世纪上都对应这一个数字,并且不相同。
  • 线程id可以用c++标准库里面的函数:std::this_thread::get_id()来获取。

2.临时对象构造时机抓捕

#include <thread>
#include <iostream>

using namespace std;

class A
{
public:
	int m_i;
	A(int a) :m_i(a)
	{
		//获取线程 id
		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)拷贝构造函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::A()析构函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
};

void my_print(const int& i, const A &pmybuf)
{
	cout << &pmybuf << endl;
	return;
}

void my_print2(const A& pmybuf)
{
	cout << "子线程 myprint 的参数地址是:" << &pmybuf <<  "---thread_id = " << std::this_thread::get_id() << endl;
}

int main()
{
	cout << "主线程 --- thread_id = " << std::this_thread::get_id() << endl;
	int mvar = 1;
	int mysecondpar = 12;
	// 不使用临时对象创建线程,则在子线程中构造对象,会导致错误
	thread mytobj(my_print2, mvar);
	// 不使用临时对象创建线程,则在子线程中构造对象,线程id和主线程不同,会导致错误
	//thread mytobj(my_print2, A(mvar));
	//使用临时对象创建线程,则在主线程中构造对象,线程id和主线程相同,没有错误
	mytobj.join();
	
	cout << "I love chain !" << endl;
	return 0;
}

如果传引用:执行一次拷贝函数
在这里插入图片描述
如果不传引用:执行两次拷贝函数
在这里插入图片描述
所以传类对象,一般要用引用传;

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

#include <thread>
#include <iostream>

using namespace std;

class A
{
public:
	mutable int m_i; //不管在什么情况下该值都可以修改
	A(int a) :m_i(a)
	{
		//获取线程 id
		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)拷贝构造函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::A()析构函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
};

void my_print(const int& i, const A& pmybuf)
{
	cout << &pmybuf << endl;
	return;
}

void my_print2(const A& pmybuf)
{
	pmybuf.m_i = 199;
	cout << "子线程 myprint 的参数地址是:" << &pmybuf << "---thread_id = " << std::this_thread::get_id() << endl;
}

int main()
{
	A myobj(10);
	thread mytobj(my_print2, myobj);
	//虽然在 my_print2 中修改了m_i,但是并没有修改myobj的m_i值,但要是类传引用要加const
	//thread mytobj(my_print2, std::ref(myobj));
	//把真正的myobj的引用传进去,则可以修改值
	mytobj.join();
	return 0;
}

std::ref函数(智能指针作为参数传递)

#include <thread>
#include <iostream>

using namespace std;

class A
{
public:
	mutable int m_i; //不管在什么情况下该值都可以修改
	A(int a) :m_i(a)
	{
		//获取线程 id
		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)拷贝构造函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::A()析构函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
};

int main()
{
	unique_ptr<int> myp(new int(100));
	thread mytobj(my_print2, std::move(myp));//把指针转到了pzn中,myp变成空的
	mytobj.join();
	//如果用detach()则有可能出错,有可能主线程都已经执行完了,但是还没有把只能呢个指针转过去
	return 0;
}

四、用成员函数指针做线程函数

#include <thread>
#include <iostream>

using namespace std;

class A
{
public:
	int m_i; //不管在什么情况下该值都可以修改
	A(int a) :m_i(a)
	{
		//获取线程 id
		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)拷贝构造函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::A()析构函数执行]" << this << "---thread_id = " << std::this_thread::get_id() << endl; }

	//线程的执行入口
	void thread_work(int num) 
	{
		cout << "[子线程 thread_work 执行]" << endl;
	}
};

int main()
{
	A myobj(10);
	//第一个为线程的入口函数,第二个为类对象,第三个为函数的参数
	std::thread mytobj(&A::thread_work, myobj, 15);
	//如果传智能指针,则用的就是myobj本身,此时用 detach() 则错了
	//std::thread mytobj(&A::thread_work, std::ref(myobj), 15);
	mytobj.join();
	return 0;
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值