问题背景
由于需要对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*)¶m);
}
//让主线程不结束
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 中进行深入了解,这里不展开。