浅谈Windows多线程编程几个常见问题

浅谈Windows多线程编程几个常见问题 本文不讨论CriticalSection,Mutex,Semaphore,Event的如何使用,这里只谈在多线程容易碰到的问题.1.函数重入关于函数重入的概念我这里就不多讲了.一般具有可重入性的函数暗含无状态特征,即函数的输出只由当前函数的输入决定而不依赖其他状态,多线程环境中,如果要防止函数重入,就要防止函数每次调用之间的数据状态依赖.比如如果你的函数修改静态的,全局的或类的成员

 

本文不讨论CriticalSection, Mutex, Semaphore, Event的如何使用,这里只谈在多线程容易碰到的问题.

1.函数重入
关于函数重入的概念我这里就不多讲了.一般具有可重入性的函数暗含无状态特征,即函数的输出只由当前函数的输入决定而不依赖其他状态,
多线程环境中,如果要防止函数重入,就要防止函数每次调用之间的数据状态依赖.比如如果你的函数修改静态的,全局的或类的成员变量时,就要注意了,可能你的每次函数调用,会对下一次调用状态产生影响.函数的局部变量修改则重入不会产生问题.
举个例子,比如有个class的函数对成员进行修改,可能多个线程对同一对象进行调用:
void CTest::Test()
{

    this->m_bStarted = FALSE;
    // Section 1….
    this->m_bStarted = TRUE;

    if (this->m_bStarted)
    {

      //Section 2….
    }
}
如果两个线程同时调用该函数,函数将不按照预期的行为执行,可能另一线程在你把m_bStarted置为TRUE后,再次进入把m_bStarted置为FALSE,本来你要执行Section 2的代码结果没执行.

2.工作线程
一个对象为了设计需要,比如Services对象,可能需要创建新的工作线,并且新建线程会访问对象的成员变量.这时要特别注意对象的销毁,否则很容易访问非法内存.因为存在这种情况,使用者把该对象delete后,并没有通知线程结束,或者先于线程销毁了对象,线程可能继续运行,并且访问该实例对象,导致非法内存访问(Access violation).

3.跨线程指针传递
这个问题不限于多线程情况,但是在多线程情况下容易发生,一个全局或者参数指针,经过多次传递,到另一个线程使用.但是指针所指对象销毁时,没有或者没办法通知到所有使用该对象的线程,也可能导致非法内存访问.一种解决方法是加Ref Counter,还有智能指针什么的,让对象通过Ref来自己管理生命周期.

4.状态同步
经常我们一个成员函数经常基于前一函数调用状态,有顺序依赖,通常如果在单线程情况下,不会有问题,但在多线程情况下,程序写得不好,经常产生问题.比如,写一个Service类,可能有下面一些methods, Startup, Work, Shutdown,现在假设只有Startup成功才能开始work,只有work的对象才需要Shutdown.
如果单线程下,你按照一定调用顺序不会有问题,
pService->Startup();
pService->Work();
pService->Shutdown();
如果是多线程,就不一定了,说不定你Startup还没完成,另一个thread正在Shutdown,肯定不是你想要的预期行为.这时你必须加一些flag去确保只有Startuped的对象才能Work,标识为Startuped或者已经worked状态的对象才需要Shutdown,类似状态机一样.
在多线程中,同步以及改变状态flag,你就要用到同步/互斥对象以及函数,例如CriticalSection, Mutex, Semaphore和Event,另外就是Interlocked**系列函数,有关Interlocked**函数,可以参照MSDN的Interlocked Variable Access.

5.死锁

实际应用中,在多线程中使用回调事件通知机制,比较容易发生死锁,比如主线程已经在处理中locked A,同时回调事件通知线程也在进行处理事件locked B,当主线程访问进行处理时,就可能会等待lock B,而回调线程此时有可能会通知主线程某些事件发生,hold住lock B, 并等待lock A,死锁发生.

线程#1在获得Lock A后,需要获得Lock B,而同时,线程#2在Lock B后,需要获得Lock A。对于线程#1和#2,由于都不能获得满足的条件,就形成死锁了。

浅谈Windows多线程编程几个常见问题
记住死锁发生的条件:Mutual exclusion, Hold and wait,No pre-emption,Circular wait.简单点就是,在使用互斥资源时,避免多个线程形成环式等待,或者一次性把所有资源全请求好,当线程所需资源不能全部获得时,最好把已占用资源释放,锁不是加的越多越好,只在需要发生资源竞争情况下使用,并且减少加锁范围, 能在被调用函数中加锁的话,就不要在调用函数中加锁,重复加锁不能使你的程序更加安全,只会增加死锁的可能, 只有member,static,global,shared data的访问和修改需要互斥,对于通过参数传递的数据上层应保证数据的同步访问,这样才能划分同步职责。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/38794.html

(0)
编程小号编程小号

相关推荐

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注