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

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

3.1.1 要避免的陷阱–解释1

#include <iostream>
#include <thread>

using namespace std;

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

int main()
{
	int mvar = 1;
	int &mvary = mvar;
	char mybuf[] = "this is a test!";

	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach();

	cout << "主线程执行结束!!!" << endl;

	return 0;
}

1、使用detach时,会不会因为主线程结束了就销毁了mvar变量,导致子线程访问不安全呢?
调试时,shift+f9查看变量的地址发现mvar的地址和i的地址不一样,分析认为i并不是mvar的引用,实际是值传递,那么我们认为,即便主线程detach了子线程,那么子线程中使用i值也是安全的。但是也不推荐使用引用,还是使用值传递
在这里插入图片描述
2、那么pmybuf是不是安全的呢?
查看地址发现地址一样,主线程执行完了,mybuf 指向的内存被系统回收,子线程再使用这个指针是不安全的,因此在detach子线程时,指针绝对会有问题
mybuf = 0x010ff9d0 "烫烫烫烫烫烫烫烫
pmybuf = 0x010ff9d0 “this is a test!”

3.1.2 要避免的陷阱–解释2

#include <iostream>
#include <thread>

using namespace std;

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

int main()
{
	int mvar = 1;
	int &mvary = mvar;
	char mybuf[] = "this is a test!";

	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach();

	cout << "主线程执行结束!!!" << endl;

	return 0;
}

1、那么使用const string& pmybuf去接受字符串数组安全吗?
在这里插入图片描述
内存地址并不同,看是好像安全,但是真的安全吗?需要清楚mybuf 是在什么时候被转化为string的,事实上存在mybuf都被回收了(main函数执行完了),系统才用mybuf去转string的可能性,那么如何修改呢?
我们直接将mybuf转换成string对象,这是一个可以保证在线程中用肯定有效的对象

#include <iostream>
#include <thread>

using namespace std;

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

int main()
{
	int mvar = 1;
	int &mvary = mvar;
	char mybuf[] = "this is a test!";

	thread mytobj(myprint, mvar, string(mybuf));
	//mytobj.join();
	mytobj.detach();

	cout << "主线程执行结束!!!" << endl;

	return 0;
}

在创建线程的同时构造临时对象的方法传递参数是可行的

3.1.3 总结

1、若传递int这种简单类型参数,建议都是值传递,不要用引用。防止节外生枝。
2、如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后在函数参数里,用引用来接,否则系统还会构造一次对象,浪费
//终极结论:
3、建议不使用detach(),只使用join(), 这样就不存在局部变量失效导致线程对内存的非法引用问题

3.2 临时对象作为线程参数(续)

3.2.1 线程id概念

1、概念:id是个数字,每个线程(不管是主线程还是子线程)实际上都对应着一个数字,而且每个线程对应的这个数字都不同。也就是说,不同的线程,它的线程id数字)必然是不同,线程id可以用c++标准库里的函数来获取std::this_thread::get_id()
3.2.2 临时对象构造时机抓捕

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};


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

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;
	int mvar = 1;

	thread mytobj(myprint, mvar);
	mytobj.join();

	return 0;
}

在这里插入图片描述
使用临时对象

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};


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

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;
	int mvar = 1;

	thread mytobj(myprint, A(mvar));
	mytobj.join();

	return 0;
}

用了临时对象后,所有的A类对象都在main()函数中就已经构建完毕了
在这里插入图片描述
此时使用mytobj.detach()就不会有问题
注意:void myprint(const A& pmybuf)要使用引用,不用引用会调用一个构造函数,两个拷贝构造函数,造成浪费,此外第二个拷贝构造还是在主线程中执行的

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

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
	mutable int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};

void myprint(const A& pmybuf)
{
	pmybuf.m_i = 199;   //我们修改该值不会影响到main函数
	cout << "子线程myprint的参数地址是" << &pmybuf << "threadid = " <<std::this_thread::get_id() << endl;
}

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;

	A myobj(10);   //生成一个类对象

	thread mytobj(myprint, myobj);  //myobj将类对象作为一个线程参数
	mytobj.join();

	return 0;
}

shift + f9
在这里插入图片描述
通过分析发现,这都不是同一个地址,虽然是引用,但是在main函数传递的myobj并没有在线程中被修改,这种写法会被拷贝一份

但是用引用,就应该被修改,这个要怎么解决呢?
使用std::ref

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
};

void myprint(A& pmybuf)
{
	pmybuf.m_i = 199;   //我们修改该值不会影响到main函数
	cout << "子线程myprint的参数地址是" << &pmybuf << "threadid = " <<std::this_thread::get_id() << endl;
}

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;

	A myobj(10);   //生成一个类对象

	std::thread mytobj(myprint, std::ref(myobj));  //myobj将类对象作为一个线程参数
	mytobj.join();

	return 0;
}

在这里插入图片描述
此时地址一样,可以实现使用引用,在main函数传递的myobj可以在线程中被修改,这种写法不会再被拷贝一份

如果是智能指针该怎么传呢?
要使用std::move

#include <iostream>
#include <thread>

using namespace std;

void myprint(unique_ptr<int> pzn)
{
	return 0;
}

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;

	unique_ptr<int> myp(new int(100));

	std::thread mytobj(myprint, std::move(myp));
	mytobj.join();

	return 0;
}

3.4 用成员函数指针作线程函数

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }

	void thread_work(int num)
	{
		cout << "[子线程thread_work执行]" << this << "threadid = " << std::this_thread::get_id() << endl;
	}
};

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;

	A myobj(10);   //生成一个类对象

	std::thread mytobj(&A::thread_work, myobj, 15);

	mytobj.join();

	return 0;
}

类对象做线程函数

#include <iostream>
#include <thread>

using namespace std;

class A
{
public:
	int m_i;
	//类型转换构造函数,可以把一个int转换成一个类A对象
	A(int a) :m_i(a) { cout << "[A::A(int a)构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	A(const A& a) :m_i(a.m_i) { cout << "[A::A(const A)拷贝构造函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }
	~A() { cout << "[A::~A()析构函数执行]" << this << "threadid = " << std::this_thread::get_id() << endl; }

	void thread_work(int num)
	{
		cout << "[子线程thread_work执行]" << this << "threadid = " << std::this_thread::get_id() << endl;
	}

	void operator()(int num)
	{
		cout << "[子线程()执行]" << this << "threadid = " << std::this_thread::get_id() << endl;
	}
};

int main()
{
	cout << "主线程id是" << std::this_thread::get_id() << endl;

	A myobj(10);   //生成一个类对象
 
	std::thread mytobj(myobj, 15);

	mytobj.join();

	return 0;
}

如果加std::ref就不会调用拷贝构造函数,但是此时要注意不要使用detach了

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值