多线程技术在我们的应用程序中经常用到,创建线程也十分简单方便,直接调用系统的API CreateThread就可以了,这里我们主要讨论线程如何封装,在不同的项目中被重复使用,这里是Windows下的封装,先贴代码:
CThreadEx.h
class CThreadEx { public: CThreadEx(void); ~CThreadEx(void); public: HANDLE m_hThread; //线程句柄 DWORD m_dwThreadID; //线程ID HANDLE m_hKillEvent; //退出线程事件句柄 DWORD m_dwDelay; //线程延时 private: //线程地址 static DWORD WINAPI ThreadProc(LPVOID lParam); public: virtual BOOL Run() = 0; //执行部分 BOOL Start(); //开始 BOOL Stop(); //停止 BOOL Delay(DWORD dwMillisecond = 1); //将线程延时 };
CThreadEx.cpp
#include "ThreadEx.h" CThreadEx::CThreadEx(void) { m_hThread = NULL; m_dwThreadID = 0; m_hKillEvent = NULL; //默认的延迟 m_dwDelay = 1; } CThreadEx::~CThreadEx(void) { } BOOL CThreadEx::Start() { //必须为空 if (NULL != m_hThread) { return TRUE; } //关闭线程信号 m_hKillEvent = CreateEvent(NULL, TRUE, FALSE, NULL); assert(NULL != m_hKillEvent); //创建线程 m_hThread = CreateThread(NULL, 0, ThreadProc, this, 0, &m_dwThreadID); assert(NULL != m_hThread); return (NULL != m_hThread); } //停止执行 BOOL CThreadEx::Stop() { //必须存在 if (NULL == m_hThread) { return FALSE; } //结束线程(触发关闭事件然后等待线程完成) SignalObjectAndWait(m_hKillEvent, m_hThread, INFINITE, FALSE); //清理工作 CloseHandle(m_hKillEvent); CloseHandle(m_hThread); m_hKillEvent = NULL; m_hThread = NULL; return TRUE; } //将线程挂起多少毫秒(如果线程提前退出,返回FALSE,否则返回TRUE) BOOL CThreadEx::Delay(DWORD dwMillisecond) { return (WAIT_OBJECT_0 != WaitForSingleObject(m_hKillEvent, dwMillisecond)); } //线程 DWORD WINAPI CThreadEx::ThreadProc(LPVOID lParam) { CThreadEx *pThreadEx = static_cast<CThreadEx *>(lParam); //直到监听到退出信号 while (pThreadEx->Delay(pThreadEx->m_dwDelay)) { if (!pThreadEx->Run()) { break; } } return 0; }
首先把线程的操作封装到CThreadEx基类里面,一共提供四个public函数,分别是Start()、Stop()、Run()和Delay(),前面两个函数看名字就能知道是什么意思,分别是启动线程和停止线程。关键是就是Run()函数,它是一个虚函数,可以被子类所重写,然后将具体的业务放到Run()里面来执行。
下面我们来分析每一个函数的具体用意和逻辑:
Start()函数,用来启动线程,先来看一下逻辑,当实例对象调用Start()函数时,先判断一下线程是否被创建,如果线程句柄不为NULL,则表示线程已经在运行了,直接返回TRUE,否则说明线程当前还没有被创建,先创建一个关闭线程的事件,用来触发它来控制线程是否退出,这个留到运行哪一个部分再详细说明。这个时候才到创建线程的API,直接调用CreateThread API就可以创建了,保存函数的返回句柄到全局变量中,并记录线程的ID,虽然这些我们一般很少用。如果线程句柄不为NULL,则表示线程创建成功了,返回TRUE,否则返回FALSE。
Stop()函数,程序在最后关闭的时候,良好的程序设计应该都要主动关闭线程,调用Stop()函数就可以了,在关闭之前先判断一下当前的线程是否存在,如果线程句柄为NULL,表明线程在之前没有创建成功或没有创建,直接返回FALSE,接下来调用系统函数SignalObjectAndWait来触发关闭事件并永久等待线程返回,在这里API调用一欠可以达到通过原子方式触发一个事件内核对象并等待线程内核对象,类似一次调用触发事件(SetEvent)之后等待事件(WaitForSignalObject),一个函数可以达到两个函数的效果,减少内核切换需要的时间。最后关闭句柄并将其设为NULL,线程就能安全退出了;细心的你可能就会发现,为什么不将关闭线程写在ThreadEx类的虚构函数里面,其实原因是因为Run函数是一个虚函数,如果被子类继承,这个时候Run函数的虚函数指针指向的是子类的Run,而虚构的时候是从子类开始的,意味着子类被虚构时候函数指针就会指向CThreadEx,而Run是一个纯虚函数,没有真正的函数地址,会就被抛出异常,甩以Stop函数应该在继承于CThreadEx的子类调用。
Run()函数,Run()函数是一个纯虚函数,意味着这个CThreadEx类是一个抽象类,必须被继承后实例化,确实实际使用情况中也是如此,一个线程一般用来执行某些功能,这个时候可以通过继承CThreadEx类,重写Run()函数,并将业务逻辑写在Run()这里,即可被调用,这也是这个类封装的意义所在。
Delay(DWORD dwMillisecond)函数,用来将程序延时指定的毫秒,类似于系统函数Sleep,避免在while(TRUE)时CPU一直被占用,这里不用Sleep而是使用事件的原因是因为Sleep函数被调用以后,在没有达到指定的时候时,程序是无法唤醒线程的,意味着线程极有可能没有得到关闭的机会,如果使用Delay,则可以在关闭时立即检测到,即使Delay(INFINITE),延时的时候无限长,也会因为线程在Stop的时候事件被触发使用Delay返回FALSE,这个时候意味着在未到延时的总时间长度就被中断,也就是程序在延时的过程中被提前退出了,这个可以将线程安全的关闭,如果坚持使用Sleep函数来放弃CPU时间,时间不宜过长。
假如线程创建成功了,ThreadProc(LPVOID lParam)函数将被执行,这里它做的事情就是等待是否有线程关闭的事件和执行Run()函数,等待的时间为1ms,避免CPU被一直占用,这里的事件就是上面提到的Start()函数被调用时创建的事件,这里插一下话,相比使用全局变量来标志线程是否退出,我觉得使用事件更好一些。
下载地址:ThreadEx.zip
本站部分资源收集于网络,纯个人收藏,无商业用途,如有侵权请及时告知!