文章目录
一、前言二、引例1、案例描述2、案例实现
三、问题分析一、输出二、分析
四、改进方案
一、前言
本文将介绍一种主线程主动 delete 子线程对象,导致主线程卡死(无限等待)的情况;
二、引例
1、案例描述
1)实现包括一个主线程和一个子线程;2)主线程负责启动子线程,并且在子线程运行 500ms 后进行销毁:3)子线程每 16ms 输出一条保活信息;
2、案例实现
子线程 LogicThread 继承 Thread(Thread 类实现参考);
#include "Thread.h"
class LogicThread
: public Thread
{
public
:
LogicThread() : m_bStop(false
) {
}
protected
:
virtual
void DoWork() {
while (!m_bStop
) {
printf("Thread[%d]: I'm active!\n", GetId());
Sleep(16);
}
printf("Thread[%d]: I'm exit!\n", GetId());
}
private
:
bool m_bStop
;
};
int main() {
LogicThread
*pLS
= new LogicThread
;
pLS
->Start();
Sleep(500);
delete pLS
;
return 0;
}
这段代码的 delete 能够顺利执行吗?答案是不能!
三、问题分析
一、输出
首先运行起来看下控制台输出,线程会无限循环的输出 I’m active!:
Thread
[70508]: I'm active
!
Thread
[70508]: I'm active
!
Thread
[70508]: I'm active
!
Thread
[70508]: I'm active
!
Thread
[70508]: I'm active
!
Thread
[70508]: I'm active
!
...
二、分析
分析的突破口就在 delete 的调用;如果这里没有 delete ,那么主线程可以顺利走到 return 0;但是加上 delete 以后,会调用到基类 Thread 的析构,从而调用子线程的 Stop() 接口,实现如下:
Thread
::~Thread() {
Stop();
}
void Thread
::Stop() {
if (!m_pkHandle
) {
return;
}
WaitForSingleObject((HANDLE
)m_pkHandle
, INFINITE
);
CloseHandle((HANDLE
)m_pkHandle
);
m_pkHandle
= NULL;
}
由于子线程一直在运行没有返回,所以 WaitForSingleObject 会一直 阻塞等待,这个调用是在主线程进行的,所以现象就是主线程卡死了;
四、改进方案
为了让 WaitForSingleObject 不会无限等待,需要确保线程函数退出以后,才能进行逻辑线程的销毁;所以可以在主线程轮询子线程状态,当子线程进入退出状态方能执行退出逻辑;为了把问题描述清楚,这里增加一个线程结束条件,计数器 100 计数完毕 则将 m_bStop 置 true 让线程停下来;
#include "Thread.h"
class LogicThread
: public Thread
{
public
:
LogicThread() : m_bStop(false
) {
}
protected
:
virtual
void DoWork() {
int nCnt
= 100;
while (!m_bStop
) {
printf("Thread[%d]: I'm active!\n", GetId());
Sleep(16);
if (--nCnt
== 0) {
m_bStop
= true
;
}
}
printf("Thread[%d]: I'm exit!\n", GetId());
}
private
:
bool m_bStop
;
};
int main() {
LogicThread
*pLS
= new LogicThread
;
pLS
->Start();
do {
Sleep(16);
} while (pLS
->IsRunning());
delete pLS
;
return 0;
}