多线程传参问题

问题背景

由于需要对zmq的连接进行监控,需要引用zmq monitor相关的API。而这个API是一个需要阻塞式的,为了不影响主线程逻辑,所以需要单独开一个线程进行监控。有了创建线程的需求。

具体问题描述

由于需要对zmq相关的连接进行线程对应线程监控,因为在创建线程的时候,我写的逻辑代码类似这样。为简单一些,我把代码原型精简,尽量与业务无关来讨论这个问题。

示例代码

//这个结构体的定义,为的是给创建线程传多个参数
struct Param {

    int i;
    string addr;
};

void* Fun(void* arg) {
    if (NULL) {
        return NULL;
    }
    Param* param = (Param*)arg;
    cout << " i = " << param->i << endl;
    cout << "addr = " << param->addr << endl;
    //printf("add ptr = %p",  param->addr.c_str());   打印地址2
}

//string addr = "hello";  代码1
//假设只需要创建一个纯种
for (int i = 0; i < 1; ++i) {
    string addr = "hello";    //代码2
    Param param;
    param.i = 100;
    param.addr = addr;
    //printf("param addr ptr = %p", param.addr.c_str());   打印地址1
    pthread_t th;
    pthread_create(&th, NULL, Fun, (void*)&param);
}

//让主线程不结束
do {
    sleep(1);
}while (true);

先说几个代码示例运行的现象。在linux下的g++ 4.8.4运行

1、这种情况下,创建线程的两个参数中,addr无法被传过去。传过去的是一个空值。

2、如果将代码1处去掉注释,将代码2处注释掉。即是把string addr=”hello”的定义移到for之前。则在Fun中,addr=”hello”能被传到新创建的线程中。

原因分析

1、正常情况下,这里创建线程的话,不应该使用一个栈变量进行传参,因为不同的线程,栈空间是独立的。这里的param的生存空间在for这个代码块中。当创建线程的时候,很可能for这个代码块已经运行完了,则其中的栈变量已经全部被销毁了。所以在新创建的线程中,无法将addr的值传过去。建议传参变量定义为static类型。

2、将string addr写到for之前,为什么就能传过去呢?param的生存周末与string addr放哪里定义有关么?

param.addr = addr,这一句不是把addr的数据给拷贝到param结构中了吗?

这里如果往深处挖一下,能发现一片别有洞天的地方。首先,我们正常的经验是,string 这种自身有堆空间的,赋值运行符,肯定是一个深拷贝的动作。如果是深拷贝,那运行结果,与我们的理论不符合,必然有一个认识是错误的。不要急,我们去证明自己的猜想。把两个变量对应的数据地址打印出来,在“打印地址1”和“打印地址2”处进行打印。

打印结果是两者的地址是一致的,则param.addr = addr,这一句并没有进行深拷贝动作。那么,就可以理解,为什么把string addr移到for之前能把参数传过去,数据能正确访问的原因了。因为移到外面,创建线程的时候,主线程对应的string addr的数据没有被销毁。

3、这里牵扯出另一个问题。为什么param.addr = addr这里没有进行深拷贝。这里涉及到stl中的一项优化技术,叫“写时复制”技术,叫copy on write技术。具体如何实现的,大家感兴趣的话,可以参考陈皓老师的分析https://coolshell.cn/articles/12199.html 中进行深入了解,这里不展开。

猜你喜欢

转载自blog.csdn.net/dreamvyps/article/details/78909492