一、线程的使用
在MFC中,一般用全局函数AfxBeginThread()来创建并初始化一个线程的运行,该函数有两种重载形式,分别用于创建工作者线程和用户界面线程。
1.创建工作者线程
CWinThread* AfxBeginThread(
AFX_THREADPROC pfnThreadProc,//PfnThreadProc:指向工作者线程的执行函数的指针,
LPVOID pParam, //线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);//用于创建工作者线程
PfnThreadProc:指向工作者线程的执行函数的指针,线程函数原型必须声明如下:
UINT ExecutingFunction(LPVOID pParam);
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的原因。一般情况下,返回0表明执行成功。
- pParam: 一个32位参数,执行函数将用某种方式解释该值。它可以是数值,或是指向一个结构的指针,甚至可以被忽略;
- nPriority: 线程的优先级。如果为0,则线程与其父线程具有相同的优先级;
- nStackSize: 线程为自己分配堆栈的大小,其单位为字节。如果nStackSize被设为0,则线程的堆栈被设置成与父线程堆栈相同大小;
- dwCreateFlags:如果为0,则线程在创建后立刻开始执行。如果为CREATE_SUSPEND,则线程在创建后立刻被挂起;
- lpSecurityAttrs:线程的安全属性指针,一般为NULL;
2.创建用户界面线程
CWinThread* AfxBeginThread(
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);
注:
pThreadClass 是指向 CWinThread 的一个
导出类
的运行时类对象的指针,该
导出类定义了被创建的用户界面线程的启动、退出等
;
其中,关于CWinThread类的数据成员及常用函数进行简要说明。
- m_hThread: 当前线程的句柄;
- m_nThreadID: 当前线程的ID;
- m_pMainWnd: 指向应用程序主窗口的指针
还可以采用
CreateThread()函数创建线程,但是返回值为BOOL值。当创建成功时,返回TRUE。
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,UINT nStackSize=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
3.创建线程的方法
- 调用AfxBeginThread()来一次性地创建并启动一个线程;
- 通过两步法来创建线程:首先创建CWinThread类的一个对象,然后调用该对象的成员函数CreateThread()来启动该线程。
二、线程同步
线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特点是同步速度特别快,适合于对线程运行速度有严格要求的场合。
内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。
内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。由于这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换一般要耗费近千个CPU周期,因此同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。
1.采用事件内核对象来保持线程同步
创建对应的事件类型对象;
HANDLE CreateEvent(
LPSECURITY_ATTRIBUTES lpEventAttributes, // 结构指针,通常为NULL
BOOL bManualReset, // TRUE:手动清除信号标志;FALSE:在WaitForSingleObject()函数之后,系统自动清除信号
BOOL bInitialState, // 初始状态。TRUE:有信号;FALSE:无信号。
LPCTSTR lpName // 事件对象的名称
);
在创建线程前,首先创建一个可以自动复位的事件内核对象hEvent,而线程函数则通过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,得以执行下一步代码,被保护的代码将得以执行。
可将事件置位SetEvent(hEvent);
注:最好采用系统自动清除信号,因为人工置位不能保证仅有一个线程获得事件对象。
使用临界区只能同步同一进程中的线程,而使用事件内核对象则可以对进程外的线程进行同步,其前提是得到对此事件对象的访问权。
MFC的构造函数可以实现CreateEvent()的功能
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
2.定义临界区量CCriticalSection来保证线程同步
临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只允许一个线程对共享资源进行访问。如果有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其他线程可以继续抢占,并以此达到用原子方式操作共享资源的目的。
MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是非常简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片段即可。
CCriticalSection定义的对象用lock()和UnLock()函数圈出了对象定义的临界区。一个对象对应一个临界区。
CCriticalSection g_clsCriticalSection; // MFC临界区类对象
char g_cArray[10]; // 共享资源
UINT ThreadProc20(LPVOID pParam)
{
g_clsCriticalSection.Lock(); // 进入临界区
for (int i = 0; i < 10; i++) // 对共享资源进行写入操作
{
g_cArray[i] = 'a';
Sleep(1);
}
g_clsCriticalSection.Unlock(); // 离开临界区
return 0;
}
UINT ThreadProc21(LPVOID pParam)
{
g_clsCriticalSection.Lock();
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
g_clsCriticalSection.Unlock();
return 0;
}
……
void CSample08View::OnCriticalSectionMfc()
{
AfxBeginThread(ThreadProc20, NULL);
AfxBeginThread(ThreadProc21, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
http://blog.csdn.net/chenyixin121738/article/details/53367223