线程传值风险
对于一个线程,通常可以给它传入一个LPVOID类型的参数。大致看来,这种行为与给一个函数传参没有多少差别。可能正是这种表象,使我们疏于防范,从而造成一些不易发觉的风险。下面用一些例子来说明。
...
struct thread_data
{
int a;
int b;
};
void thread_func(LPVOID lpVoid);
void test_func()
{
thread_data td;
memset(&td, 0, sizeof thread_data);
td.a = 1;
td.b = 2;
_beginthread(thread_func, 0, (LPVOID)&td);
}
int _tmain(int argc, _TCHAR* argv[])
{
test_func();
getchar();
return 0;
}
void thread_func(LPVOID lpVoid)
{
thread_data* pTd = (thread_data*)lpVoid;
cout << "thread_data::a = " << pTd->a << endl;
cout << "thread_data::b = " << pTd->b << endl;
return;
}
输出结果:
thread_data::a = 262661272
thread_data::b = 2095484
可以看出这个值是随机的。原因显而易见,将一个局部变量的地址,传递给了线程。在test_func函数返回后,如果再访问这个地址,将会出现“undefined behavior”,但是不会报错。因为这个局部变量的地址存在于栈内存中,栈内存是随线程一起分配的,主线程的栈内存整个程序结束前一直都存在而且尺寸也不会变。所以,保存局部变量的内存会一直存在,只是内容会被其他变量修改。因此 ,当一个局部变量被销毁后,再次通过它的地址去访问它的内容,可能会正确(见下面的例子),也可能会错误,是一个不确定的行为。这样的操作是不允许的!
//将上面的test_func()函数调用,改成下面形式
int _tmain(int argc, _TCHAR* argv[])
{
{
thread_data td;
memset(&td, 0, sizeof thread_data);
td.a = 1;
td.b = 2;
_beginthread(thread_func, 0, (LPVOID)&td);
}
getchar();
return 0;
}
输出结果:
thread_data::a = 1
thread_data::b = 2
结果正确!
总结:除非能保证局部变量一直存在于线程生命周期内,否则不能将一个局部变量的地址传递给线程,这样会造成不确定的行为。一般,我们会采用全局变量或者堆内存传递给线程,这样可以保证传递内容的可靠性。
在给线程传递参数的过程中,还存在一个不容忽视的问题,那就是参数的同步性。假如我们将一个全局变量传入一个线程后,然后,又无意识地在其它地方修改了它,这就会使程序运行结果不在预期内。见下面的例子:
struct thread_data
{
int a;
int b;
};
void thread_func(LPVOID lpVoid);
void test_func(thread_data * ptd)
{
ptd->b = 3;
}
int _tmain(int argc, _TCHAR* argv[])
{
thread_data * ptd = new thread_data;
memset(ptd, 0, sizeof thread_data);
ptd->a = 1;
ptd->b = 2;
_beginthread(thread_func, 0, (LPVOID)ptd);
test_func(ptd);
getchar();
return 0;
}
void thread_func(LPVOID lpVoid)
{
thread_data* pTd = (thread_data*)lpVoid;
Sleep(100);
cout << "thread_data::A::_x = " << pTd->a << endl;
cout << "thread_data::b = " << pTd->b << endl;
return;
}
输出结果:
thread_data::a = 1
thread_data::b = 3
可能我们预期的结果为1,2!
如果不涉及到非要使用指针对象,我们可以在线程函数中,做一些小的改变去避免这种现象的发生,实现简单的同步操作。见下面例子:
int _tmain(int argc, _TCHAR* argv[])
{
thread_data * ptd = new thread_data;
memset(ptd, 0, sizeof thread_data);
ptd->a = 1;
ptd->b = 2;
_beginthread(thread_func, 0, (LPVOID)ptd);
Sleep(10); //增加休息时间,让线程先运行
test_func(ptd);
getchar();
return 0;
}
void thread_func(LPVOID lpVoid)
{
//不使用指针赋值,采用内存拷贝,这样后面涉及形参的运算就不会受到影响了
//thread_data* pTd = (thread_data*)lpVoid;
thread_data td = *(thread_data*)lpVoid;
Sleep(100);
cout << "thread_data::A::_x = " << td.a << endl;
cout << "thread_data::b = " << td.b << endl;
return;
}
输出结果:
thread_data::a = 1
thread_data::b = 2