C/C++ ThreadBug

 

在C++多线程开发过程中难免会遇到很多意想不到的问题,最近遇到了参数传递的问题,总结一下。

参数传递如果是临时变量,就用malloc或new来申请变量,不然在线程运行时,临时变量可能会被释放,但是线程里面的是野指针。

举例 代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
#include <iostream>
#include <pthread.h>
#include <vector>
using namespace std;

void *func(void *args)
{
vector<int> *vec = (vector<int>*) args;
cout<<"vec size:"<<vec->size()<<endl;
for(vector<int>::iterator it = vec->begin();it!=vec->end();it++)
{
cout<<"输出 "<<*it<<endl;
}

}

int main()
{
pthread_t thread_ids[2];
for(int i=0;i<2;i++)
{
vector<int> vec;//临时变量出循环会被释放
for(int j=0;j<3;j++)
{
vec.push_back(i*j);
}

int ret=pthread_create(&thread_ids[i],NULL,func,(void*)&vec);
if(ret!=0)
{
cout<<"Create Thread Fail!"<<endl;
return -1;
}

}

for(int i=0;i<2;i++)
{
pthread_join(thread_ids[i],NULL);//等待线程结束
}

return 0;
}

结果输出
结果

与预期相差很多。

原因分析:
vec是临时变量,作为参数传递给线程。pthread_create会立刻返回,第一层for循环会很快结束,vec会被释放,此时线程拿到的参数是个野指针,输出结果就不是预期了。

解决办法,临时变量vecnew或者malloc出来,但是要注意内存泄漏的问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <pthread.h>
#include <vector>
using namespace std;

void *func(void *args)
{
vector<int> *vec = (vector<int>*) args;
cout<<"vec size:"<<vec->size()<<endl;
for(vector<int>::iterator it = vec->begin();it!=vec->end();it++)
{
cout<<*it<<endl;
}

}

int main()
{
pthread_t thread_ids[2];
for(int i=0;i<2;i++)
{
//new出来的变量出循环不会被释放,但是什么时候delete?
vector<int> *vec = new vector<int>();

for(int j=0;j<3;j++)
{
vec->push_back(i*j);
}

int ret=pthread_create(&thread_ids[i],NULL,func,(void*)vec);
if(ret!=0)
{
cout<<"Create Thread Fail!"<<endl;
return -1;
}
}

for(int i=0;i<2;i++)
{
pthread_join(thread_ids[i],NULL);//等待线程结束
}

return 0;
}

输出结果如下图,是正确的:

phread_join会等线程结束,正确使用pthread_join可以防止临时变量被提前释放。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <iostream>
#include <pthread.h>
#include <vector>

using namespace std;

void *func(void *args) {
vector<int> *vec = (vector<int> *) args;
cout << "vec size:" << vec->size() << endl;
for (vector<int>::iterator it = vec->begin(); it != vec->end(); it++) {
cout << "输出 " << *it << endl;
}

}

void test() {
pthread_t thread_id;

vector<int> vec;
for (int j = 0; j < 3; j++) {
vec.push_back(j);
}

int ret = pthread_create(&thread_id, NULL, func, (void *) &vec);
if (ret != 0) {
cout << "Create Thread Fail!" << endl;
return;
}

}

int main() {
test();
return 0;
}

vec离开test()函数也会被释放掉,导致程序出现不可预期的错误。

如果在test()中加上pthread_join函数,在test()中等待线程结束,此时vec一直有效,输出结果就是正确的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include <iostream>
#include <pthread.h>
#include <vector>

using namespace std;

void *func(void *args) {
vector<int> *vec = (vector<int> *) args;
cout << "vec size:" << vec->size() << endl;
for (vector<int>::iterator it = vec->begin(); it != vec->end(); it++) {
cout << "输出 " << *it << endl;
}

}

void test() {
pthread_t thread_id;

vector<int> vec;
for (int j = 0; j < 3; j++) {
vec.push_back(j);
}

int ret = pthread_create(&thread_id, NULL, func, (void *) &vec);
if (ret != 0) {
cout << "Create Thread Fail!" << endl;
return;
}

pthread_join(thread_id,NULL);//等待线程结束,保证临时变量vec在线程执行中不会被test提前释放

}

int main() {
test();
return 0;
}

谨慎使用STL作为线程参数

在使用vector等STL时,如果传递给线程参数的其中的某个元素地址,由于vector会根据元素多少动态申请内存,之前传递给线程的地址就有可能会失效,导致了非预期效果出现。

看下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#include <iostream>
#include <pthread.h>
#include <vector>
#include <zconf.h>

using namespace std;

void *func(void *args) {
int *val = (int *)args;
sleep(1);//使效果更明显
cout<<*val<<endl;
}

int main() {

pthread_t thread_ids[5];

vector<int> vec;
for (int i = 0; i < 5; i++) {
cout<<"capactity:"<<vec.capacity()<<endl;
vec.push_back(i);
int ret = pthread_create(&thread_ids[i], NULL, func, (void *) &(vec[i]));
if (ret != 0) {
cout << "Create Thread Fail!" << endl;
return -1;
}

}

for(int i=0;i<5;i++)
{
pthread_join(thread_ids[i], NULL);//等待线程结束
}

return 0;
}

输出结果:

可以发现输出的与预期差很多。

这个错误比较隐蔽:当vec中只有0,1的时候,capacity大小为2,此时空间已经满了。vec.push_back(2),会重新申请空间,此时之前传递给pthread_createvec[1]的地址就会失效,此时打印就会出错。

也会有人问,如果传递的迭代器呢,会不会出现这个问题?
在STL源码剖析这本书中,提到过对vector的任何操作,一旦引起空间重新配置,指向原vector的所有迭代器都失效了。所以传递迭代器也会出现这个问题。这个问题也引申出来了,在使用vector作为外层循环的时候不要在循环中会引起空间重新配置的操作。