c++多线程如何传递参数(值传递,引用传递)
转自 chen沉尘【C++多线程】 感谢作者,我只是个搬运工
线程最直接的理解就是“轻量级进程”,它是一个基本的CPU执行单元,也是程序执行流的最小单元,由线程ID、程序计数器、寄存器集合和堆栈组成。线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。
线程可以共享进程的内存空间,每个线程都拥有自己的堆栈,所以本文主要分析的问题就是如何在线程中像函数一样传递参数。
关于参数的传递,std::thread的构造函数只会单纯的复制传入的变量,特别需要注意的是传递引用时,传入的是值的副本,也就是说子线程中的修改影响不了主线程中的值。
值传递
主线程中的值,被拷贝一份传到了子线程中。可以看出下面主线程和子线程参数的地址是不同的,因而子线程中值的改变不会对主线程中的值产生影响。
#include <iostream>
#include <thread>
using namespace std;
void test(int ti, int tj)
{
cout << "子线程开始" << endl;
//ti的内存地址0x0055f69c {4},tj的内存地址0x0055f6a0 {5}
cout << ti << " " << tj << endl;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
//i的内存地址0x001efdfc {4},j的内存地址0x001efdf0 {5}
int i = 4, j = 5;
thread t(test, i, j);
t.join();
cout << "主线程结束!" << endl;
return 0;
}
传引用
首先要说明的是,按照普通函数的做法,如果我们在函数传入参数的地方加上引用传递的符号&时候,就可以实现我们的目的了,但是在多线程里面是不行的,因为线程的创建属于函数式编程,即使是用引用来接收传的值,也是会将其拷贝一份到子线程的独立内存中。
下面是例子:
可以看出下面的例子中,虽然void test(const int &ti, const A &t)中我们使用了&,但是仍旧ti以及t的地址与主进程中的不一致,显然这样的做法并不能达到我们期望的目的。
#include <iostream>
#include <thread>
using namespace std;
class A{
public:
int ai;
A (int i): ai(i) { }
};
//这种情况必须在引用前加const,否则会出错。目前本人的觉得可能是因为临时对象具有常性
void test(const int &ti, const A &t)
{
cout << "子线程开始" << endl;
//ti的内存地址0x0126d2ec {4},t.ai的内存地址0x0126d2e8 {ai=5 }
cout << ti << " " << t.ai << endl;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
//i的内存地址0x010ff834 {4},a的内存地址0x010ff828 {ai=5 }
int i = 4;
A a = A(5);
thread t(test, i, a);
t.join();
cout << "主线程结束!" << endl;
return 0;
}
为了解决上面的问题,才引入了std::ref()。但是注意如果我们会在子线中改变它,此时用于接收ref()的那个参数前不能加const. 绿色冰点C++11 std::ref使用场景
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int ai;
A(int i) : ai(i) { }
};
//接收ref()的那个参数前不能加const,因为我们会改变那个值
void test(int& ti, const A& t)
{
cout << "子线程开始" << endl;
cout << ti << " " << t.ai << endl;
ti++;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
int i = 4;
A a = A(5);
thread t(test, ref(i), a);
t.join();
cout << "i改变:" << i << endl;
cout << "主线程结束!" << endl;
return 0;
}
传指针
怎么可能少的了指针传递 >.<
当指针传递的时候,主线程和子线程中的指针都是指向同一块内存。所以在这种情况下会有一个陷阱,如果使用detach(),则当主线程崩溃或者正常结束后,该块内存被回收,若此时子线程没有结束,那么子线程中指针就成了野指针,程序会出错。
#include <iostream>
#include <thread>
using namespace std;
void test(char *p)
{
cout << "子线程开始" << endl;
//0x004ffeb4 "hello"
cout << p << endl;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
//0x004ffeb4 "hello"
char s[] = "hello";
thread t(test, s);
t.join();
cout << "主线程结束!" << endl;
return 0;
}
传临时对象
用临时变量作为实参时,会更高效,由于临时变量会隐式自动进行移动操作,这就减少了整体构造函数的调用次数。而一个命名变量的移动操作就需要std::move()。
#include <iostream>
#include <thread>
using namespace std;
class A {
public:
int ai;
A (int i) : ai(i)
{
cout << "构造" << this << endl;
}
A (const A& a) :ai(a.ai) {
cout << "拷贝构造" << this << endl;
}
~A()
{
cout << "析构" << this << endl;
}
};
void test(const A& a)
{
cout << "子线程开始" << endl;
cout << "子线程结束" << endl;
return;
}
int main()
{
cout << "主线程开始" << endl;
int i = 4;
thread t(test, A(i));
t.join();
cout << "主线程结束!" << endl;
return 0;
}
总结
1、使用引用和指针时要注意,尤其是将子线程detach后,如果主线程先结束,其内存空间会被收回,子线程中的指针就成了野指针;
2、对于内置简单类型,建议传值;
3、对于类对象,建议使用引用来接收,因为使用引用会只会构造两次,而传值会构造三次;
4、在detach下要避免隐式转换,因为此时子线程可能还来不及转换主线程就结束了,应该在构造线程时,用参数构造一个临时对象传入。
本文详细介绍了C++多线程中参数传递的不同方式,包括值传递、引用传递、指针传递以及临时对象的传递。通过实例展示了在多线程环境下,如何正确地传递和共享数据,强调了使用std::ref()解决引用传递问题以及指针传递时可能遇到的野指针问题。总结了不同情况下的最佳实践,如内置类型建议传值,类对象使用引用来减少构造次数,并在detach时避免使用隐式转换。
542

被折叠的 条评论
为什么被折叠?



