C++多线程编程入门2

在mfc中创建和结束线程

上一篇讲到在mfc中,我们创建线程用AfxBeginThread函数比较合适。接下来利用mfc对话框程序来说明线程的创建和结束过程。

新建的对话框类名为CMFCMultiThreadDlg,首先在界面上画三个按钮,分别是启动、暂停和关闭线程。再画一个编辑框控件作为线程函数的输出显示,代码如下:
public:
	CEdit m_edtOutput;//编辑框控件
	int m_Cnt;//输出数据的行数
	afx_msg void OnBnClickedButton1();//启动线程
	afx_msg void OnBnClickedButton2();//关闭线程
	afx_msg void OnBnClickedButton3();//暂停线程

考虑到线程函数必须是静态或者全局的,而静态成员函数无法访问类中的非静态成员变量,因此我们要创建一个静态成员函数作为线程入口函数,然后将对话框类的指针作为参数传入,并在该函数中通过指针调用线程处理函数,即该对话框类的成员函数。代码如下:
public:
	CWinThread* m_MyThread;//mfc线程类
	static DWORD MyThreadDataOutput(LPVOID *lpvParam);//线程入口
	void DataOutput();//线程处理函数负责输出数据

	afx_msg void OnClose();//对话框退出

记得在OnInitDialog中初始化m_Cnt = 0;函数实现部分代码如下:
void CMFCMultiThreadDlg::DataOutput()
{
	while(1)
	{
		CString str;
		str.Format("正在计数:%d\r\n", m_Cnt++);
		if(m_Cnt > 1000)//输出行数大于1000行则清空内容
		{
			m_edtOutput.SetWindowTextA("");
			m_Cnt = 0;
		}
		else
		{
			m_edtOutput.SetSel(-1);
			m_edtOutput.ReplaceSel(str);//编辑框内容以追加方式写入
		}
	}
}


DWORD CMFCMultiThreadDlg::MyThreadDataOutput(LPVOID *lpvParam)
{
	CMFCMultiThreadDlg *dlg = (CMFCMultiThreadDlg*)lpvParam;
	dlg->DataOutput();
	return NULL;
}


void CMFCMultiThreadDlg::OnBnClickedButton1()//启动线程
{
	// TODO: 在此添加控件通知处理程序代码
	m_MyThread = AfxBeginThread((AFX_THREADPROC)CMFCMultiThreadDlg::MyThreadDataOutput, (LPVOID)this);
}


void CMFCMultiThreadDlg::OnBnClickedButton2()//关闭线程
{
	// TODO: 在此添加控件通知处理程序代码
	if(m_MyThread)
    {
		TerminateThread(m_MyThread->m_hThread, 0);
		CloseHandle(m_MyThread->m_hThread);
	}
}


void CMFCMultiThreadDlg::OnBnClickedButton3()//暂停线程
{
	// TODO: 在此添加控件通知处理程序代码
	static bool isSuspending = false;
	if(!isSuspending)
		m_MyThread->SuspendThread();
	else
		m_MyThread->ResumeThread();
	isSuspending = !isSuspending;
}


void CMFCMultiThreadDlg::OnClose()
{
	// TODO: 在此添加消息处理程序代码和/或调用默认值
	if(m_MyThread)
	{
		TerminateThread(m_MyThread->m_hThread, 0);
		CloseHandle(m_MyThread->m_hThread);
	}
	CDialogEx::OnClose();
}

界面如图

要说明的是I nitInstance和 ExitInstance只适用于用户界面线程的初始化和终止,这里我们创建的线程是工作者线程,用不到这两个函数。利 用轻量级进程线程查看器ProcessThreadsView查看线程创建情况,可以清楚看到本程序生成的线程所有状态。讲到这里貌似我们的任务已经完成,但如果用户调皮,不按套路出牌,如线程暂停或者执行的时候我们点击启动线程按钮,在线程没有启动的时候暂停或者关闭线程,反复启动关闭线程等等操作,好吧(用户是一群可耐的魔鬼),接下来就来谈谈如何提高本程序的健壮性和安全性:我们可以在 OnInitDialog中初始化线程类指针 m_MyThread 为NULL,在启动、暂停、关闭线程的时候判断指针是否为空来选择合适的执行方式即可,并在关闭线程中置 m_MyThread为NULL,这样做基本应付了以上的三种情况。但是!!!通过任务管理器可以明显看到每次在启动关闭线程的操作中发生了4kB的内存泄露如下:
Detected memory leaks!
Dumping objects ->
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\thrdcore.cpp(306) : {747} client block at 0x01099E38, subtype c0, 68 bytes long.
a CWinThread object at $01099E38, 68 bytes long
f:\dd\vctools\vc7libs\ship\atlmfc\src\mfc\thrdcore.cpp(306) : {741} client block at 0x01099BE8, subtype c0, 68 bytes long.
a CWinThread object at $01099BE8, 68 bytes long
Object dump complete.
调试的过程中可以看到在关闭线程函数结束的时候,m_MyThread里面的内容并没有得到释放,然后被我们强制赋成了NULL,当然有内存泄露。于是我想到利用设置CWinThread类中的标志位m_bAutoDelete为FALSE来手动delete释放资源,然而...并没有什么用,泄露依旧发生。根据上篇文章的介绍,我们还是老老实实让线程函数自己返回来释放资源吧,具体做法如下:

方法1: 建一个全局变量或者主对话框类成员变量(本文程序只有一个对话框,且线程函数都是在这一个对话框类中实现,变量资源是共享的,作用相当于一个全局变量,若涉及到多个对话框、线程访问多个不同类资源时则应该建立全局变量)

新建一个bool变量m_isEnd初始化为true,将DataOutput函数中循环条件改成while(m_isEnd),关闭线程函数体改成:
void CMFCMultiThreadDlg::OnBnClickedButton2()//关闭线程
{
	// TODO: 在此添加控件通知处理程序代码
	m_isEnd = false;

	if(m_MyThread) 
    <span style="white-space:pre">	</span>{
		WaitForSingleObject(m_MyThread->m_hThread, INFINITE);//等待线程结束
		CloseHandle(m_MyThread->m_hThread);
		m_MyThread = NULL;
	}
}
程序执行到WaitForSingleObject时主线程将一直等待,直到次线程执行结束返回,这样就能让线程自己退出了。但是(是的,现实是残酷的!),程序执行到这一句后进入了无限等待中...然而调试的结果告诉我m_MyThread线程执行结束并返回了,就在我百思不得其解中,MSDN给了我答案:
Use caution when calling the wait functions and code that directly or indirectly creates windows. If a thread creates any windows, it must process messages. Message broadcasts are sent to all windows in the system. A thread that uses a wait function with no time-out interval may cause the system to become deadlocked. For example, theDynamic Data Exchange (DDE)protocol and the COM function CoInitialize both indirectly create windows that can cause adeadlock. Therefore, if you have a thread that creates windows, use MsgWaitForMultipleObjects or MsgWaitForMultipleObjectsEx, rather than WaitForSingleObject.
也就是 说如果线程函数中处理了有关消息响应,发生了窗口数据交换等操作都可能导致主线程阻塞的情况。
解决方式就是用MsgWaitForMultipleObjects替代WaitForSingleObject,即将该句代码替换为:
DWORD dwRet = 0;
MSG msg;
while(TRUE)
{
<span style="white-space:pre">	</span>dwRet = MsgWaitForMultipleObjects(1, &m_MyThread->m_hThread, FALSE, INFINITE, QS_ALLINPUT);
	switch(dwRet)
	{
	case WAIT_OBJECT_0:
		break;
	case WAIT_OBJECT_0 + 1:
		//从队列中取出消息并交给窗口处理
		PeekMessage(&msg, NULL, 0, 0, PM_REMOVE);
		DispatchMessage(&msg);
		continue;
	default:
		break;
	}
	break;
}
通过switch选择将未处理的消息处理完毕后再跳出循环,若没有switch则会导致该函数执行结束后仍有消息在队列中,排队直到窗口处理。

修改后程序成功执行。
(对于二者的区别在博客http://blog.csdn.net/silvervi/article/details/5874212中有详细讲到!)
实际上在这其中并没有起到什么实质性的作用,只是定期返回而已,那么据此可以用固定时间的WaitForSingleObject来代替MsgWaitForMultipleObjects ,并且循环等待,让线程执行函数接收消息并返回。

注意,这时候又会产生一个新的问题,如果在线程挂起的状态下关闭线程,这时候MsgWaitForMultipleObjects并不会阻塞(它会以为线程执行结束或者挂掉了),但实际上线程不会结束执行,占用的资源并未释放,结果再次发生内存泄露,如果设置函数第三个参数标志位为TRUE,则函数会一直发生阻塞,用户失去对整个程序的控制权。所以,在关闭线程的时候我们应该判断线程是否处于挂起的状态,那么如果一个线程还活着,怎样鉴别一个线程是正在执行还是挂起呢?令人遗憾的是,微软没有给出直接的接口函数。还好MFC里有两个函数SuspendThread和ResumeThread。微软给每个线程维护着一个叫做挂起次数计数器的东西,这个在上面两个函数中有大用处。

SuspendThread是使线程挂起,调用后,返回该次调用之前的挂起计数值,然后挂起计数值加1。如果返回为0,则表示线程原先正在执行,现在进入挂起状态。
ResumeThread是使线程唤醒,调用该函数后,线程此时并不一定能摆脱挂起状态。调用该函数后,返回该次调用之前的挂起计数值,然后挂起计数值减1,如果是原先挂起计数值已经为0则保持0值。如果返回值为1,则表示线程原先被挂起,现在转入正在执行状态。如果返回为0,则表示线程原先就处于正在执行状态,现在也继续执行。当返回值大于1时,挂起技术值减1,但线程将依旧被挂起,在这种情况下,只有继续调用几次该函数制止返回值为1,线程才能拜托挂起继续执行。
所以在关闭线程函数中我用了一句判断句:

while(m_MyThread && m_MyThread->ResumeThread() > 1);
即可满足需求,当然了,你也可以在对话框类中声明一个bool变量m_isSuspend,通过这个标志即可随时判断线程的状态。


方法2:
1、在对话框中创建一个Event句柄,初始化自动事情并置为无信号状态:
hExitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
2、线程函数中等待事件有信号才返回,并做好释放工作:
if(WaitForSingleObject(hExitEvent, 0) != WAIT_TIMEOUT)
{
	m_edtOutput.SetWindowTextA("");
	m_Cnt = 0;
	break;
}
3、关闭线程函数中设置事件为有信号状态:
if(m_MyThread)
{
	::SetEvent(hExitEvent);
	CloseHandle(m_MyThread->m_hThread);
	m_MyThread = NULL;
}

OK,到此我们完成了线程的创建和终止,消除了内存泄露和明显的bug等问题。整个工程代码(VS2010开发)和软件见链接:(免积分下载)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值