C++日记——Day37:线程传参详解,detach()大坑,成员函数做线程函数

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

void myprint(const int &i, char *pmybuf) //分析发现,i并不是myvar的引用,实际是值传 
                                         //递,那么我们认为,即便主线程detach了子线 
                                         //程,那么子线程中用i值仍然是安全的
{
    cout << i << endl;
    cout << pmybuf << endl;         //指针在detach子线程时,绝对会有问题
}

int main() {
    int myvar = 1;
    int &mvary = myvar;
    char mybuf[] = "goog luck";
    thread myobj(myprint, myvar, mybuf); 
    thread myobj(myprint, myvar, string(mybuf)); //这种写法是安全的
    myobj.join();
    cout << "end" << endl;
    return 0;
}

分析发现,i并不是myvar的引用,实际是值传 递,那么我们认为,即便主线程detach了子线程,那么子线程中用i值仍然是安全的。

指针在detach子线程时,绝对会有问题

mybuf到底是什么时候转换成string的,事实上存在,mybuf被回收以后,系统采用mybuf去转换string对象得情况

thread myobj(myprint, myvar, string(mybuf)); 

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

事实1:只要在用临时构造的类对象作为参数传递给线程,那么就一定都能够在主线程执行完毕前,把线程函数的第二个参数构造出来从而确保即便detach了,子线程也安全运行。

 

总结:

1、若传递简单类型参数,建议都用值传递,不要引用。

2、如果传递类对象,避免隐式类型转换。全部都在创建线程这一行就构建出临时对象来,然后再函数参数里用引用来接,否则系统还会多构造一次临时对象,浪费性能;(主要针对detach场景)

void myprint(const int &i, const string &pmybuf)

3、建议不适用detach,只是用join,这样就不存在局部变量失效,导致线程对内存的非法引用

 

线程ID

id是个数字,每个线程实际上都对应着一个数字,而且每个线程对应的数字都不同,也就是说不同的线程id必然是不同的;

线程id可以通过C++标准库里的函数来获取。std::this_thread::get_id()

 

临时对象的构造时机抓捕

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

	~A() {
		cout << "A::A(int a)析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
	}
};

void myprint2(const A &mybuf) { //这里建议加const
	cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
}

int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	int var = 1;
	std::thread myobj(myprint2, var);
	myobj.join();
}

上面代码运行结果:

由运行结果可知,这个构造类型转换构造函数是在子线程中执行的,那么这就带来了一个问题,如果使用detach,则有可能在主线程执行完毕,释放var时,子线程还没有构造A对象,那么这种情况就会导致程序异常 

现在修改代码后

int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	int var = 1;
	std::thread myobj(myprint2, A(var));
	myobj.join();
}

 在主线程中调用类型转换构造函数构造A临时对象后

发现A对象的构造在主线程中完成了,这样就避免了,主线程执行完var被释放的情况

 

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

void myprint2(const A &mybuf) { //这里建议加const
    mybuf.m_i = 10;
    cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
}

上面代码虽然传递的参数是引用,但是当它在子线程中执行时并不会影响主线程的变量。也就是说mybuf仍然是拷贝出来的,不是主线程传递对象的引用。

void myprint2(const A &mybuf) { //这里建议加const
	cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
}

int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	A obj(1);
	std::thread mythread(myprint2, std::ref(obj));
	myobj.join();
}

当我们使用了std::ref(),可以看到,就可以把主线程的参数传递到子线程中,不会再构造对象副本。 

 

智能指针作为参数传递

void myprint2(const unique_ptr<A> &mybuf) {
	mybuf->m_i = 4;
	cout << "子线程的参数地址" << &mybuf << std::this_thread::get_id() << endl;
}

int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	auto a = make_unique<A>(1);
	std::thread myobj(myprint2, std::move(a));
	myobj.join();
}

 

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

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

	~A() {
		cout << "A::A(int a)析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
	}

	void thread_work(int num){
		cout << "子线程thread_work启动" << endl;
	}
};


int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	int var = 10;
	A a(1);  //生成一个类对象
	std::thread myobj(&A::thread_work, a, var);  //注意:第二个参数是一个类对象
	myobj.join();
	return 0;
}

在主线程中又执行了拷贝构造函数

int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	int var = 10;
	A a(1);  //生成一个类对象
        //std::thread myobj(&A::thread_work, &a, var);
	std::thread myobj(&A::thread_work, std::ref(a), var);  //注意:第二个参数是一个类对象
	myobj.join();
	return 0;
}

增加std::ref()后 

 

用可调用对象作为线程函数

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

	~A() {
		cout << "A::A(int a)析构函数执行" << this << "threadid=" << std::this_thread::get_id() << endl;
	}

	void thread_work(int num){
		cout << "子线程thread_work启动" << endl;
	}

	void operator()(int num) {
		cout << "子线程()执行" << endl;
	}
};

int main() {
	cout << "主线程id" << std::this_thread::get_id() << endl;
	int var = 10;
	A a(1);  //生成一个类对象
	std::thread myobj(a, var);
    //std::thread myobj(std::ref(a), var); 将不会再调用拷贝构造函数而是把本身作为线 
                                         //程函数
	myobj.join();
	return 0;
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值