C++11多线程thread参数传递问题 - Go语言中文社区

C++11多线程thread参数传递问题


目录

写在前面

thread类的构造函数

join函数

detach函数

thread中参数传递

类对象作为参数

类中函数作为参数

参考书籍


写在前面

多线程在很多地方都是必须要掌握的方法,这里先说一下,thread对象的参数传递问题

thread类的构造函数

thread() noexcept;       //default constructor   其中noexcept表示函数不会抛出异常,如果抛出异常程序就会终止

template <class Fn, class... Args> 
explicit thread (Fn&& fn, Args&&... args);   //initialization constructor  explicit 表示不支持隐式转换

thread (const thread&) = delete;        //copy constructor delete表示不生成默认拷贝构造函数,并且可以禁止使用某个函数,也就表示不可以使用一个线程初始化另一个线程

thread (thread&& x) noexcept;    //move constructor

join函数

join()函数有两个作用,①、等待子线程执行完毕,主线程才结束执行;②、清理子线程相关的存储器,是std::thread不再与子线程相关联,释放子线程中的资源

join函数需要考虑的问题是,什么时候写join函数,如果在使用join函数之前,程序因为异常问题导致终止,没有调用join函数,导致内存泄露问题等等

detach函数

detach()函数,让线程在后台运行主,意味着线程不能与之产生直接交互,后台线程的归属和控制都由C++运行时库处理。

detach函数需要考虑的问题是,确保子线程中的参数必须为对象的复制,因为可能主线程退出导致临时对象实现,子线程对象相继实现,出现不可预料的问题。

thread中参数传递

正如上面的初始化构造函数,我们传递参数将参数依次放在初始化构造函数中能够即可

#include <iostream>
#include <thread>

void foo(const int  &x,char *mychar)
{
	std::cout << &x << "   " << &mychar << std::endl;
	std::cout << "正在运行的线程为:" << std::this_thread::get_id() << "线程的参数为: " << x <<"  "<<mychar<< std::endl;
	return;
}

int main()
{
	std::cout << "主线程的线程id为: " << std::this_thread::get_id() << std::endl;
	
	int x = 1;
	char mybuff[] = "This is a test";
	std::cout << &x << "   " << &mybuff << std::endl;
	std::thread second(foo, x, mybuff);
	second.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

对于x,使用的是引用传递,但是数字对象地址在主线程和子线程中地址不同。要在子线程中共享数据x,即地址相同,必须使用std::ref进行修饰,下面再在类对象作为参数传递时会有更详细说明

这样可以将参数传递到子线程中,并且我们可以发现子线程中的字符对象地址和传递进入的字符地址是相同的,所以我们可以推断指针这里使用的是浅层拷贝,两个指针指向的是同一个地址,所以这里的字符串时共享的,我们这里可以这样解决:

#include <iostream>
#include <string>
#include <thread>

void foo(const int  &x,const std::string &mychar)   //这里将char*转换为string,首先隐式转换为string,然后将string复制拷贝到子线程中
{
	//std::cout << &x << "   " << &mychar << std::endl;
	std::cout << "正在运行的线程为:" << std::this_thread::get_id() << "线程的参数为: " << x <<"  "<<mychar<< std::endl;
	return;
}

int main()
{
	std::cout << "主线程的线程id为: " << std::this_thread::get_id() << std::endl;
	
	int x = 1;
	char mybuff[] = "This is a test";
	//std::cout << &x << "   " << &mybuff << std::endl;
	std::thread second(foo, x, mybuff);
	second.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

这里在子线程的函数参数中使用的是const string的引用,这样就首先在函数中将char*隐式转换为string,然后将string传送到子线程中进行操作

但是如果使用detach函数,还是有可能会产生问题,因为不确定什么时候进行饮食转换,可能在主线程结束后,隐式转换还没有开始,mybuff就已经失效了,这样依然会产生不可预料的结果

解决方法:

在主线程中进行显示转换,这样可以保证一定可以在主线程中进行显示转换:

	std::thread second(foo, x, std::string(mybuff));   //只要在主线程中进行显示转换就可以解决上面所说的问题

下面我们就对上面的说法进行验证:

#include <iostream>
#include <string>
#include <thread>
#include <mutex>
class A
{
public:
	A(int _a = 0) :a(_a)      //转换函数
	{
		std::cout << "构造函数执行,执行他的线程为:" << std::this_thread::get_id() << std::endl;
	}
	A(const A& x) :a(x.a)
	{
		std::cout << "拷贝构造函数执行,执行他的线程为:" << std::this_thread::get_id() << std::endl;
	}
	~A()
	{
		std::cout << "析构函数执行,执行他的线程为:" << std::this_thread::get_id() << std::endl;
	}
	friend std::ostream &operator <<(std::ostream &os, const A &a);  //因为ostream没有拷贝构造函数,所以必须传递引用类型的ostream

	//void operator()()    //将类对象作为参数,需要重载运算符()
	//{
	//	std::cout << "作为类参数对象正在执行,执行的线程为:" << std::this_thread::get_id() << std::endl;
	//	return;
	//}
private:
	int a;
};
std::ostream &operator <<(std::ostream &os, const A &a)
{
	os << a.a << std::endl;
	return os;
}

void foo(const int  &x,const A &a)
{
	std::cout << "子线程正在运行,线程id为:" << std::this_thread::get_id() << "线程的参数为: " << x << "  " << a << std::endl;
	return;
}

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

	int num = 5;
	std::thread first(foo,num,num);
	first.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

这样我们发现,隐式转换是在子线程中进行的,但是我们只要增加显示转换后:

std::thread first(foo,num,A(num));

首先对象现在主线程中进行构造,并且将拷贝对象传到子线程中,实现对象的分离,所以这样即使在最后使用detcah函数,也不会早场引用已经析构的对象。

类对象作为参数

如果需要将类对象作为参数,需要在类中重载()运算符。栗子如下:

#include <iostream>
#include <thread>

class A
{
public:
	A(int _a = 0) :a(_a) 
	{
		std::cout << "构造函数执行,执行他的线程为:" << std::this_thread::get_id() << std::endl;
	}
	A(const A& x):a(x.a)
	{
		std::cout << "拷贝构造函数执行,执行他的线程为:" << std::this_thread::get_id() << std::endl;
	}
	~A()
	{
		std::cout << "析构函数执行,执行他的线程为:" << std::this_thread::get_id() << std::endl;
	}
	void operator()()    //将类对象作为参数,需要重载运算符()
	{
		a--;
		std::cout << "作为类参数对象正在执行,执行的线程为:" << std::this_thread::get_id() << "a的值为:"<< a <<std::endl;
		return;
	}
	void print_a()
	{
		std::cout << "a的值为: " << a << std::endl;
	}
private:
	int a;
};

int main()
{
	std::cout << "主线程的线程id为: " << std::this_thread::get_id() << std::endl;
	A a(5);
	a.print_a();
	//std::thread first((A()));使用默认构造的未命名的变量,要在外面增阿基括号,将其解释为对象 
	std::thread first(a);
	first.join();

	a.print_a();
	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

输出结果为:

可以看到,函数对象首先在主线程中做一次拷贝构造然后将拷贝后的对象传入线程中,在子线程中对对象进行修改也不会影响主线程中的对象

考虑一下:如果我们将first.join()换成first.detach(),这里会不会因为主线程提前退出,对象a提前销毁,对子线程中的对象造成影响?

正如我们上面解释的,传入子线程中的是在主线程中复制的对象,所以这里我们可以使用detach和join作用是相同的。

如果要将主线程对象本身传入子线程中,我们需要在传入参数中添加std::ref(a)即可,这里就必须要使用first.join(),防止出现上面所说的问题。

类中函数作为参数

#include <iostream>
#include <string>
#include <thread>
#include <list>
#include <mutex>

class MesProcess
{
public:
	void Inmsglist()
	{
		for (int i = 0; i < 100000; i++)
		{
			std::cout << "Inmsglist线程正在执行,插入数字为:" << i << std::endl;
			msglist.push_back(i);
		}
	}

	void Outmsglist()
	{
		int command;
		for (int i = 0; i < 100000; i++)
		{
			if (!msglist.empty())
			{
				command = msglist.front();
				msglist.pop_front();
				std::cout << "Outmsglist线程正在执行,取出数字为:" << command << std::endl;
			}
			else
			{
				std::cout << "Outmsglist线程正在执行,但是消息队列为空" << std::endl;
			}
		}
	}
private:
	std::list<int> msglist;
};

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

	MesProcess mpobj;
	std::thread Outmsg_thread(&MesProcess::Outmsglist, &mpobj);
	std::thread Inmsg_thread(&MesProcess::Inmsglist, &mpobj);
	
	Inmsg_thread.join();
	Outmsg_thread.join();

	std::cout << "主线程运行结束" << std::endl;
	return 0;
}

首先说明:因为程序中没有对数据进行保护,所以一定会出现错误,出现错误的时间不一定

我们这一要说的是类中函数作为线程参数,我们这里还需要传递一个类对象,因为我们知道在类中的函数存在一个默认参数,就是对象本身this,所以这里要将对象传递进去

参考书籍

《c++并发编程实战》 Anthony Williams著

《c++primer puls第六版》

《C++11并发与多线程视频课程》 视频地址:http://edu.51cto.com/course/15287.html

 

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/li1615882553/article/details/85342101
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。
  • 发表于 2019-08-27 17:20:52
  • 阅读 ( 1365 )
  • 分类:Linux

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢