c++多线程如何传递参数(值传递,引用传递)

本文详细介绍了C++多线程中参数传递的不同方式,包括值传递、引用传递、指针传递以及临时对象的传递。通过实例展示了在多线程环境下,如何正确地传递和共享数据,强调了使用std::ref()解决引用传递问题以及指针传递时可能遇到的野指针问题。总结了不同情况下的最佳实践,如内置类型建议传值,类对象使用引用来减少构造次数,并在detach时避免使用隐式转换。

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下要避免隐式转换,因为此时子线程可能还来不及转换主线程就结束了,应该在构造线程时,用参数构造一个临时对象传入。

转自 chen沉尘【C++多线程】

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值