目录
一. 线程说明
1. 概念
线程是进程中的一个实体,是被系统独立分配和调度的基本单位。也有说,线程是CPU可执行调度的最小单位。
2. 线程与进程
线程是进程的可执行单元,是计算机分配CPU机时的基本单元。一个进程可以包含一个或多个线程,进程是通过线程去执行代码的。同一个进程的多个线程共享该进程的资源和操作系统分配给该进程的内存空间。每个进程必须有一个主线程,主线程退出之后该进程也就退出了。一个进程的主线程是由系统创建的。
3. 执行
在单CPU中,表面上看好像是多个进程中的多个线程共同执行,实际上是操作系统根据调度规则、依次的 将一个一个的线程可执行代码加载进CPU中执行的;即,CPU在同一时刻只能执行一段代码,由于CPU的频率非常快,迅速的在各个线程间进行切换,所以给人的感觉就好像是多个线程共同执行。
不过在多核CPU的电脑中,确实是多个线程共同执行……
所以,通过多线程,一个进程的应用程序可以充分利用CPU资源,但什么事情都是物极必反,如果线程开的太多,系统就会增加额外的开销去进行线程的调度,反而降低了CPU的使用效率。
二. windows下的各种线程
1. win32线程
(1) CreateThread
具体的函数使用可以配合msdn,这里不作函数说明,后面的都是如此。下面只说下线程回调函数:只能是全局或静态成员函数。来看看具体实现
void CthreadTestDlg::DoThread(void* pvPrm)
{
CthreadTestDlg* pobjThreadTest = static_cast<CthreadTestDlg*>(pvPrm);
pobjThreadTest->RunThread();
}
void CthreadTestDlg::RunThread()
{
int iNum = 0;
while (100 > iNum)
{
printf("thread num: %d \n", iNum++);
}
}
void CthreadTestDlg::OnBnClickedBtnWin32()
{
if (nullptr == m_hThread)
m_hThread = CreateThread(nullptr, 0, (LPTHREAD_START_ROUTINE)DoThread, (LPVOID)this, 0, 0);
}
... ...
//清理
if (nullptr != m_hThread) CloseHandle(m_hThread);
(2) _beginthreadex
还是要说下线程回调函数:全局或静态成员函数,且需要添加_stdcall限制。下面看看实现:
unsigned _stdcall CthreadTestDlg::DoThread2(void* pvPrm)
{
CthreadTestDlg* pobjThreadTest = static_cast<CthreadTestDlg*>(pvPrm);
pobjThreadTest->RunThread();
return 0;
}
void CthreadTestDlg::RunThread()
{
int iNum = 0;
while (100 > iNum)
{
printf("thread num: %d \n", iNum++);
}
}
void CthreadTestDlg::OnBnClickedBtnWin322()
{
if (nullptr == m_hThread2)
m_hThread2 = (HANDLE)_beginthreadex(nullptr, 0, DoThread2, this, 0, 0);
}
... ...
//清理
if (nullptr != m_hThread2) CloseHandle(m_hThread2);
(3) 如何选择使用哪个线程函数
C运行库中有许多全局变量,如错误码等。多个线程同时操作这些全局变量,就会出错(没同步)。为了解决未同步问题,微软想出了个办法,就是给每个线程划分了自己存全局变量的空间,TLS(thread local storage)。这样,各用各的,就不会出错了。奇怪的是,CreateThread压根没TLS的概念,就不会创建这个内存。当在CreateThread创建的线程中要读写那些全局变量时,发现没有TLS,就会创建一个给自己用。可恨的是,CloseHandle中不会释放TLS。这样,内存就泄漏了。所以,为了保险起见,创建线程使用C运行库中的_beginthreadex。
2. mfc线程
(1) 工作线程:AfxBeginThread
本质上是对CreateThread进行封装,且AfxBeginThread可设置线程优先级,同时对比win32线程,不需要CloseHandle。线程回调函数:全局或静态成员变量。下面看看实现:
UINT CthreadTestDlg::DoThreadAfx(void* pvPrm)
{
CthreadTestDlg* pobjThreadTest = static_cast<CthreadTestDlg*>(pvPrm);
pobjThreadTest->RunThread();
return 0;
}
void CthreadTestDlg::RunThread()
{
int iNum = 0;
while (100 > iNum)
{
printf("thread num: %d \n", iNum++);
}
}
//AfxBeginThread不需要CloseHandle
void CthreadTestDlg::OnBnClickedBtnMfc()
{
if (nullptr == m_pThreadAfx)
m_pThreadAfx = AfxBeginThread(DoThreadAfx, this, THREAD_PRIORITY_NORMAL);//可以设置线程优先级
}
(2) 界面线程
① 从 CWinThread 类派生自己的子类:CUIThreadApp;
② 重载 InitInstance(必须重载) 与 ExitInstance(可选重载函数),系统帮我们重载了,这一步可以省略;
③ 在 InitInstance 函数中进行界面的创建,且要return FALSE, 否则线程退出不了;
④ 开启界面线程:AfxBeginThread(RUNTIME_CLASS(CUIThread))。
// 继承了CWindThread派生类CUIThread
... ...
#define DLG_MODAL (1)
BOOL CUIThread::InitInstance()
{
#if DLG_MODAL //模态对话框的创建
CDlgTest objDlgTest;
objDlgTest.DoModal();
return FALSE;
#elif //非模态对话框的创建
m_pobjDlgTest = new CDlgTest;
m_pobjDlgTest->Create(IDD_DLG_THREAD, nullptr);
m_pobjDlgTest->ShowWindow(SW_SHOW);
m_pobjDlgTest->RunModalLoop();
return FALSE;
//return TRUE; //这里return true,则线程不会退出
#endif
return TRUE;
}
int CUIThread::ExitInstance()
{
#if DLG_MODAL
#elif
if (m_pobjDlgTest)
{
m_pobjDlgTest->DestroyWindow();
delete m_pobjDlgTest;
}
#endif
return CWinThread::ExitInstance();
}
... ...
AfxBeginThread(RUNTIME_CLASS(CUIThread));
(3) 使用说明
虽然mfc定义了界面线程,但实际项目中还是不建议在线程里操作界面,原因:
① 同一进程中的多线程是资源共享的,在多线程中操作界面会发生意想不到的结果,特别是像窗口的销毁等。
② 我们在设计代码时,是希望界面层与业务层是弱耦合关系,以实现单层模块的"高内聚",所以如果线程需要与界面层通信,可以使用PostMessage进行关联。