1.由CWinThread派生的CTestThread线程类
CTestThread.h文件
#pragma once
// CTestThread
class CTestThread : public CWinThread
{
DECLARE_DYNCREATE(CTestThread)
public:
CTestThread(); // 动态创建所使用的受保护的构造函数
virtual ~CTestThread();
public:
virtual BOOL InitInstance();
virtual int ExitInstance();
protected:
DECLARE_MESSAGE_MAP()
// Handler 虚拟、按钮、TTL控制 量产消息
afx_msg void OnHandlerTest(WPARAM wParam, LPARAM lParam);
// ... ...
// 其他消息,略过。
};
CTestThread.cpp文件
// TestThread.cpp : 实现文件
//
#include "stdafx.h"
#include "..\DynamicATE.h"
#include "..\MainFrm.h"
#include "..\DynamicATEDoc.h"
#include "..\DynamicATEView.h"
#include "..\MessageInfo.h"
#include "TestThread.h"
#define PMANAGER(x) ((CDynamicATEApp*)(AfxGetApp()))->Get##x##ManagerPointer()
// CTestThread
IMPLEMENT_DYNCREATE(CTestThread, CWinThread)
CTestThread::CTestThread()
{
}
CTestThread::~CTestThread()
{
}
BOOL CTestThread::InitInstance()
{
// TODO: 在此执行任意逐线程初始化
return TRUE;
}
int CTestThread::ExitInstance()
{
// TODO: 在此执行任意逐线程清理
return CWinThread::ExitInstance();
}
BEGIN_MESSAGE_MAP(CTestThread, CWinThread)
ON_THREAD_MESSAGE(WM_HANDLER_TEST, OnHandlerTest)
// ... ...
// 其他消息,略过。
END_MESSAGE_MAP()
// CTestThread 消息处理程序
// Handler 虚拟、按钮、TTL控制 量产消息
void CTestThread::OnHandlerTest(WPARAM wParam, LPARAM lParam)
{
PMANAGER(Handler)->HandlerTest();
}
// ... ...
// 其他处理消息,略过。
2.Handler接口管理类中的Handler测试线程的开始、结束控制以及线程执行体
在HandlerManager.h中,一些本文需要的变量、函数声明,如下:
protected:
BOOL m_bHandlerThread; // 标志是否处于Handler线程中
//BOOL m_bCanStopThread; // 标志是否可关闭Handler线程
// Handler类型 -1 - 初始 0 - Virtual 1 - ControlPad 2 - TTL
int m_iHandlerType;
int m_iBtnType; // 按键类型 0 - 左键 1 - 右键 2 - 双键 3 - 任意单键
int m_iSigType; // 信号类型 0 - 电平 1 - 脉冲沿
WORD m_iBinValue; // BIN分箱值
CTestThread* m_pHandlerThread; // Handler线程指针
public:
// TRUE - 开启Handler测试线程; FALSE - 关闭Handler测试线程;
void HandlerEnable(BOOL bEnable);
void HandlerTest(void); // Handler 测试处理过程
bool StartOfTest(void); // 接受测试开始信号 SOT
void EndOfTest(void); // 发送测试结束信号 EOT 在HandlerManager.cpp文件中的HandlerEnable()函数、HandlerTest()函数,如下:
void CHandlerManager::HandlerEnable(BOOL bEnable)
{
if(bEnable){
if(m_pHandlerThread){
delete m_pHandlerThread; m_pHandlerThread = NULL;
}
m_bHandlerThread = TRUE;
m_bCanStopThread = FALSE;
m_pHandlerThread = new CTestThread;
m_pHandlerThread->CreateThread();
// 发送Handler测试消息,开始线程循环体HandlerTest()
m_pHandlerThread->PostThreadMessageA(WM_HANDLER_TEST, NULL, NULL);
}
else{
m_bHandlerThread = FALSE;
PMANAGER(Test)->SetTesting(FALSE);
/*断点1*/while(!m_bCanStopThread){};// !!! Release下死循环点:问题1
DWORD exitCode = 0;
GetExitCodeThread(m_pHandlerThread->m_hThread, &exitCode);
TerminateThread(m_pHandlerThread->m_hThread, exitCode); //线程终止:问题2
delete m_pHandlerThread; m_pHandlerThread = NULL;
}
}
void CHandlerManager::HandlerTest()
{
// 若信号类型为脉冲沿,则开始前初始化NSTn、STnEN设置
if(m_iSigType == 1){
WORD nst = m_bSotSlope ? 0x0000 : 0x00c0;// 决定SOT有效沿的方向
WORD eot = m_bEotSlope ? 0x0000 : 0x0030;// 设置EOT为非有效状态
POINTER(SYSINTF)->Write(0x8087, eot | nst | 0x0000);
POINTER(SYSINTF)->Write(0x8087, eot | nst | 0x0003);
}
int _test_cnt = 0;
// 线程循环过程
// 若Handler线程标志为TRUE,线程一直执行
// 该标志也可用事件Event和WaitSingleObject来处理
while(m_bHandlerThread){
if(StartOfTest()){// 若接收到SOT信号
// 若信号类型为电平,则接收到SOT信号后结束上次测试的BIN信号
if(m_iSigType == 0){
// 结束BIN信号,m_bBinLevel:true - 0x0000; false - 0xffff
POINTER(SYSINTF)->Write(0x8086, m_bBinLevel ? 0x0000 : 0xffff);
}
// 设置正在测试中标志为TRUE
PMANAGER(Test)->SetTesting(TRUE);
// 发送测试消息,进行测试
PMANAGER(Test)->GetTestThreadPointer()->PostThreadMessageA(
(WM_TRTF_TEST + PMANAGER(Test)->GetTestHead()), NULL, NULL);
// 循环等待测试中标志变为FALSE
while(PMANAGER(Test)->IsTesting()){}
// 发送BIN信号、EOT信号
EndOfTest();
if(m_iHandlerType == 0) _test_cnt++; // Virtual Handler模式下 测试次数自加1
}
// Virtual Handler模式下 测试次数超过m_iTestCnt线程退出
if(m_iHandlerType == 0 && _test_cnt >= m_iTestCnt)
break;
}
/*断点2*/m_bCanStopThread = TRUE;
}
void CHandlerManager::HandlerTest() // 简化版本。
{
while(m_bHandlerThread){
if(StartOfTest()){// 若接收到SOT信号
// ... ...
// 发送测试消息,具体略过。
// ... ...
EndOfTest();
}
if(condition1)
break;
}
/*断点2*/m_bCanStopThread = TRUE;
}
3.问题描述及解决思路
Debug模式下,HandlerEnable函数、HandlerTest函数 运行良好,断点1、断点2 逻辑正常,从未出错!!! Release模式下,出现问题,当HandlerEnable(FALSE)函数执行完以下两句
m_bHandlerThread = FALSE;
PMANAGER(Test)->SetTesting(FALSE); 可以让HandlerTest()函数的循环体顺利执行到 “断点2” 处(m_bCanStopThread = TRUE),并使标志发生变化,可是当程序继续执行,运行到 “断点1” ( while(!m_bCanStopThread) { }; )时陷入了死循环!!!
1.请教别人
“断点2”处标志和“断点1”处的循环,是为了知道HandlerTest线程已正常退出循环过程,正常执行完毕。 有人说1:你怎么能用TerminateThread函数强制终止线程呢?为什么不让它自然退出? 有人说2:你怎么能写这么多死循环呢?想知道线程正常退出为什么不用WaitForSingleObject等函数呢?
仅说明以上两种比较多的建议和思路,他们的建议都是好的,再次感谢!!但最终他们都没有解释出,为什么Debug下逻辑可行运行正常而Release下会在“断点1”处出现死循环?
2.解决CWinThread派生类正常退出问题
CTestThread类是由CWinThread类派生的用户界面线程,就是一个带消息循环的工作者线程。工作者线程是以线程函数执行完毕return返回作为线程结束的判断,一般认为线程函数return就是线程退出了。而用户界面线程的结束机制是不同于工作者线程的,一般用户界面线程是响应消息来执行消息处理函数,但是消息处理函数执行完毕,并不代表线程结束;发现一个问题就是用 TerminateThread 函数强制终止线程,并不能执行到CTestThread的 ExitInstance 函数,那是不是想正常退出CTestThread就需要让线程执行到ExitInstance函数?CTestThread线程被创建后,就处于CWinThread::Run里的消息循环中了,先看CWinThread::Run的实现机制 ( 如下 ) 。可以看出想正常退出线程,先要正常退出Run函数,在Run函数 do...while...循环中第一个if分支:if ( ! PumpMessage( ) ) return ExitInstance( ); 如果线程接收到WM_QUIT消息,则执行ExitInstance函数退出Run函数。
// main running routine until thread exits
int CWinThread::Run()
{
ASSERT_VALID(this);
_AFX_THREAD_STATE* pState = AfxGetThreadState();
// for tracking the idle time state
BOOL bIdle = TRUE;
LONG lIdleCount = 0;
// acquire and dispatch messages until a WM_QUIT message is received.
for (;;)
{
// phase1: check to see if we can do idle work
while (bIdle &&
!::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE))
{
// call OnIdle while in bIdle state
if (!OnIdle(lIdleCount++))
bIdle = FALSE; // assume "no idle" state
}
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
//if (IsIdleMessage(&m_msgCur))
if (IsIdleMessage(&(pState->m_msgCur)))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
}
}
正常退出解决方法:CTestThread线程想正常退出,给线程发送WM_QUIT消息即可,则HandlerTest()函数中断点2处 m_bCanStopThread = TRUE修改为发送WM_QUIT消息,HandlerEnable函数 结束线程 部分用WaitForSingleObject函数等待WM_QUIT消息执行完毕。当然这样也就避免了m_bCanStopThread
标志的使用出现的问题。
// HandlerTest()函数中
/*断点2*/m_pHandlerThread->PostThreadMessageA(WM_QUIT, NULL, NULL);
// HandlerEnable函数中 结束线程部分
m_bHandlerThread = FALSE;
PMANAGER(Test)->SetTesting(FALSE);
WaitForSingleObject(m_pHandlerThread->m_hThread, INFINITE);
delete m_pHandlerThread;
m_pHandlerThread = NULL;
经过上述修改后,在Release模式下,线程在结束时,可以顺利执行到delete m_pHandlerThread;
恰恰这句话又出现了中断问题。设断点调试发现,原因是CWinThread线程类有个标志m_bAutoDelete可以决定在线程收到WM_QUIT结束线程后自行销毁CWinThread的句柄等资源,由于m_pHandlerThread是派生类CTestThread的指针,虽然m_pHandlerThread指针下原属于CWinThread的句柄等资源都已经被销毁了,并不代表m_pHandlerThread指针就为NULL,这样执行delete
m_pHandlerThread;时是想删除一个已经不完整的指针,肯定中断报错。
解决delete m_pHandlerThread; 中断问题方法:在线程创建之初,先Suspend挂起线程,把m_bAutoDelete函数设为FALSE,再Resume继续线程。这样CWinThread执行WM_QUIT消息后不执行自销毁过程,delete m_pHandlerThread;执行时就能顺利删除一个完整的CTestThread。
m_pHandlerThread = new CTestThread;
m_pHandlerThread->CreateThread(CREATE_SUSPENDED);
m_pHandlerThread->m_bAutoDelete = FALSE;
m_pHandlerThread->ResumeThread();
至此,正常退出CWinThread派生线程的问题已解决。3.Release下会在“断点1”处出现死循环的原因和解决方法
为了找到最最初始问题:为什么Debug下逻辑可行运行正常而Release下会在“断点1”处出现死循环? 对“断点1”处代码,在Debug和Release模式下反汇编Debug模式下,断点1的反汇编如下
// BOOL m_bCanStopThread; 标志声明,Debug模式下“断点1”反汇编
while(!m_bCanStopThread){};
005E320C mov eax,dword ptr [ebp-14h]
005E320F cmp dword ptr [eax+8],0
005E3213 jne CHandlerManager::HandlerEnable+197h (5E3217h)
005E3215 jmp CHandlerManager::HandlerEnable+18Ch (5E320Ch)
Release模式下,断点1的反汇编如下
// BOOL m_bCanStopThread; 标志声明,Release模式下“断点1”反汇编
0047F83E mov eax,dword ptr [esi+8]
while(!m_bCanStopThread){};
0047F841 cmp eax,ebx
0047F843 je CHandlerManager::HandlerEnable+0E1h (47F841h) 对比 Debug模式 和 Release模式 下,断点1的反汇编,发现Debug模式下while循环内判断每次处理实际地址ptr [eax + 8]的m_bCanStopThread标志,而Release模式下,被优化为早在执行while语句前先把实际地址ptr [esi + 8] 的m_bCanStopThread
先存到了寄存器eax中,虽然在 “断点2” 处m_bCanStopThread已变为TRUE,但是寄存器eax的值并未得到更新,while语句每次循环时,是跟寄存器eax中的数据比较,因此陷入死循环!!!出现问题的原因:Release模式下,优化程序为了使程序性能提高,常把一些变量放在寄存器中(类似于 register 关键字),而其他进程只能对该变量所在的内存进行修改,而寄存器中的值没变。尤其是多线程的,或者你发现某个变量的值与预期的不符而你确信已正确的设置了,则很可能遇到这样的问题。这种错误有时也会表现为程序在最快优化出错而最小优化正常。
解决问题的方法:使用关键字 volatile 声明,volatile BOOL m_bCanStopThread; C++中关键字volatile详解:http://blog.csdn.net/monkey07118124/article/details/50536729 多线程下的volatile:当两个线程都要用到某一个变量且该变量的值会被改变时,应该用volatile声明,该关键字的作用是防止优化编译器把变量从内存装入CPU寄存器中。如果变量被装入寄存器,那么两个线程有可能一个使用内存中的变量,一个使用寄存器中的变量,这会造成程序的错误执行。volatile的意思是让编译器每次操作该变量时一定要从内存中真正取出,而不是使用已经存在寄存器中的值。
Release模式下,断点1处的反汇编。显示表明用volatile关键字后,while循环每次都从内存中取值比较运算。
// volatile BOOL m_bCanStopThread; 标志声明,Release模式下反汇编
while(!m_bCanStopThread){};
0047F840 mov eax,dword ptr [esi+8]
0047F843 test eax,eax
0047F845 je CHandlerManager::HandlerEnable+0E0h (47F840h) 至此,Release下会在”断点1“处出现死循环的问题已解决,但最终未使用m_bCanStopThread标志。
本文详细阐述了C++线程类在退出机制上的问题,特别是CWinThread派生类如何正常退出,包括使用PostThreadMessage、WaitForSingleObject等函数解决死循环问题,并引入volatile关键字避免寄存器优化带来的错误。通过实例分析,提供了解决方案,确保线程在Release模式下也能正确退出。

649

被折叠的 条评论
为什么被折叠?



