线程传值风险

线程传值风险

对于一个线程,通常可以给它传入一个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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值