1、MFC对多线程编程的支持编程
MFC中有两类线程,分别称之为工做者线程和用户界面线程。两者的主要区别在于工做者线程没有消息循环,而用户界面线程有本身的消息队列和消息循环。
工做者线程没有消息机制,一般用来执行后台计算和维护任务,如冗长的计算过程,打印机的后台打印等。用户界面线程通常用于处理独立于其余线程执行以外的用户输入,响应用户及系统所产生的事件和消息等。但对于Win32的API编程而言,这两种线程是没有区别的,它们都只需线程的启动地址便可启动线程来执行任务。
在MFC中,通常用全局函数AfxBeginThread()来建立并初始化一个线程的运行,该函数有两种重载形式,分别用于建立工做者线程和用户界面线程。两种重载函数原型和参数分别说明以下:数组
(1) CWinThread* AfxBeginThread( 安全
AFX_THREADPROC pfnThreadProc,
LPVOID pParam,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
);//用于建立工做者线程服务器
PfnThreadProc:指向工做者线程的执行函数的指针,线程函数原型必须声明以下: 网络
UINT ExecutingFunction(LPVOID pParam);多线程
请注意,ExecutingFunction()应返回一个UINT类型的值,用以指明该函数结束的缘由。通常状况下,返回0代表执行成功。 函数
(2) CWinThread* AfxBeginThread( 性能
CRuntimeClass* pThreadClass,
int nPriority = THREAD_PRIORITY_NORMAL,
UNT nStackSize = 0,
DWORD dwCreateFlags = 0,
LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL
); spa
pThreadClass 是指向 CWinThread 的一个导出类的运行时类对象的指针,该导出类定义了被建立的用户界面线程的启动、退出等;其它参数的意义同形式1。使用函数的这个原型生成的线程也有消息机制,在之后的例子中咱们将发现同主线程的机制几乎同样。操作系统
下面咱们对CWinThread类的数据成员及经常使用函数进行简要说明。
BOOL CWinThread::CreateThread(DWORD dwCreateFlags=0,UINT nStackSize=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);
该函数中的dwCreateFlags、nStackSize、lpSecurityAttrs参数和API函数CreateThread中的对应参数有相同含义,该函数执行成功,返回非0值,不然返回0。
通常状况下,调用AfxBeginThread()来一次性地建立并启动一个线程,可是也能够经过两步法来建立线程:首先建立CWinThread类的一个对象,而后调用该对象的成员函数CreateThread()来启动该线程。
virtual BOOL CWinThread::InitInstance();
重载该函数以控制用户界面线程实例的初始化。初始化成功则返回非0值,不然返回0。用户界面线程常常重载该函数,工做者线程通常不使用InitInstance()。
virtual int CWinThread::ExitInstance();
在线程终结前重载该函数进行一些必要的清理工做。该函数返回线程的退出码,0表示执行成功,非0值用来标识各类错误。同InitInstance()成员函数同样,该函数也只适用于用户界面线程。
2、MFC中线程同步
在程序中使用多线程时,通常不多有多个线程能在其生命期内进行彻底独立的操做。更多的状况是一些线程进行某些处理操做,而其余的线程必须对其处理结果进行了解。正常状况下对这种处理结果的了解应当在其处理任务完成后进行。
若是不采起适当的措施,其余线程每每会在线程处理任务结束前就去访问处理结果,这就颇有可能获得有关处理结果的错误了解。例如,多个线程同时访问同一个全局变量,若是都是读取操做,则不会出现问题。若是一个线程负责改变此变量的值,而其余线程负责同时读取变量内容,则不能保证读取到的数据是通过写线程修改后的。
为了确保读线程读取到的是通过修改的变量,就必须在向变量写入数据时禁止其余线程对其的任何访问,直至赋值过程结束后再解除对其余线程的访问限制。象这种保证线程能了解其余线程任务处理结束后的处理结果而采起的保护措施即为线程同步。
线程的同步可分用户模式的线程同步和内核对象的线程同步两大类。用户模式中线程的同步方法主要有原子访问和临界区等方法。其特色是同步速度特别快,适合于对线程运行速度有严格要求的场合。
内核对象的线程同步则主要由事件、等待定时器、信号量以及信号灯等内核对象构成。因为这种同步机制使用了内核对象,使用时必须将线程从用户模式切换到内核模式,而这种转换通常要耗费近千个CPU周期,所以同步速度较慢,但在适用性上却要远优于用户模式的线程同步方式。
1.临界区
临界区(Critical Section)是一段独占对某些共享资源访问的代码,在任意时刻只容许一个线程对共享资源进行访问。若是有多个线程试图同时访问临界区,那么在有一个线程进入后其余全部试图访问此临界区的线程将被挂起,并一直持续到进入临界区的线程离开。临界区在被释放后,其余线程能够继续抢占,并以此达到用原子方式操做共享资源的目的。
临界区在使用时以CRITICAL_SECTION结构对象保护共享资源,并分别用EnterCriticalSection()和LeaveCriticalSection()函数去标识和释放一个临界区。所用到的CRITICAL_SECTION结构对象必须通过InitializeCriticalSection()的初始化后才能使用,并且必须确保全部线程中的任何试图访问此共享资源的代码都处在此临界区的保护之下。不然临界区将不会起到应有的做用,共享资源依然有被破坏的可能。
CRITICAL_SECTION g_cs; // 临界区结构对象
char g_cArray[10]; // 共享资源
UINT ThreadProc10(LPVOID pParam)
{
EnterCriticalSection(&g_cs); // 进入临界区
for (int i = 0; i < 10; i++) // 对共享资源进行写入操做
{
g_cArray[i] = 'a';
Sleep(1);
}
LeaveCriticalSection(&g_cs); // 离开临界区
return 0;
}
UINT ThreadProc11(LPVOID pParam)
{
EnterCriticalSection(&g_cs);
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
LeaveCriticalSection(&g_cs);
return 0;
}
……
void CSample08View::OnCriticalSection()
{
InitializeCriticalSection(&g_cs); // 初始化临界区
AfxBeginThread(ThreadProc10, NULL); // 启动线程
AfxBeginThread(ThreadProc11, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
在使用临界区时,通常不容许其运行时间过长,只要进入临界区的线程尚未离开,其余全部试图进入此临界区的线程都会被挂起而进入到等待状态,并会在必定程度上影响。程序的运行性能。尤为须要注意的是不要将等待用户输入或是其余一些外界干预的操做包含到临界区。若是进入了临界区却一直没有释放,一样也会引发其余线程的长时间等待。换句话说,在执行了EnterCriticalSection()语句进入临界区后不管发生什么,必须确保与之匹配的LeaveCriticalSection()都可以被执行到。能够经过添加结构化异常处理代码来确保LeaveCriticalSection()语句的执行。虽然临界区同步速度很快,但却只能用来同步本进程内的线程,而不可用来同步多个进程中的线程。
MFC为临界区提供有一个CCriticalSection类,使用该类进行线程同步处理是很是简单的,只需在线程函数中用CCriticalSection类成员函数Lock()和UnLock()标定出被保护代码片断便可。对于上述代码,可经过CCriticalSection类将其改写以下:
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);
}
2.事件内核对象
在前面讲述线程通讯时曾使用过事件内核对象来进行线程间的通讯,除此以外,事件内核对象也能够经过通知操做的方式来保持线程的同步。对于前面那段使用临界区保持线程同步的代码可用事件对象的线程同步方法改写以下:
HANDLE hEvent = NULL; // 事件句柄
char g_cArray[10]; // 共享资源
UINT ThreadProc12(LPVOID pParam)
{
WaitForSingleObject(hEvent, INFINITE); // 等待事件置位
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
SetEvent(hEvent); // 处理完成后即将事件对象置位
return 0;
}
UINT ThreadProc13(LPVOID pParam)
{
WaitForSingleObject(hEvent, INFINITE);
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
SetEvent(hEvent);
return 0;
}
……
void CSample08View::OnEvent()
{
hEvent = CreateEvent(NULL, FALSE, FALSE, NULL); // 建立事件
SetEvent(hEvent); // 事件置位
AfxBeginThread(ThreadProc12, NULL); // 启动线程
AfxBeginThread(ThreadProc13, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
在建立线程前,首先建立一个能够自动复位的事件内核对象hEvent,而线程函数则经过WaitForSingleObject()等待函数无限等待hEvent的置位,只有在事件置位时WaitForSingleObject()才会返回,被保护的代码将得以执行。对于以自动复位方式建立的事件对象,在其置位后一被WaitForSingleObject()等待到就会当即复位,也就是说在执行ThreadProc12()中的受保护代码时,事件对象已是复位状态的,这时即便有ThreadProc13()对CPU的抢占,也会因为WaitForSingleObject()没有hEvent的置位而不能继续执行,也就没有可能破坏受保护的共享资源。在ThreadProc12()中的处理完成后能够经过SetEvent()对hEvent的置位而容许ThreadProc13()对共享资源g_cArray的处理。这里SetEvent()所起的做用能够看做是对某项特定任务完成的通知。
使用临界区只能同步同一进程中的线程,而使用事件内核对象则能够对进程外的线程进行同步,其前提是获得对此事件对象的访问权。能够经过OpenEvent()函数获取获得,其函数原型为:
HANDLE OpenEvent(
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 指向事件对象名的指针
);
若是事件对象已建立(在建立事件时须要指定事件名),函数将返回指定事件的句柄。对于那些在建立事件时没有指定事件名的事件内核对象,能够经过使用内核对象的继承性或是调用DuplicateHandle()函数来调用CreateEvent()以得到对指定事件对象的访问权。在获取到访问权后所进行的同步操做与在同一个进程中所进行的线程同步操做是同样的。
若是须要在一个线程中等待多个事件,则用WaitForMultipleObjects()来等待。WaitForMultipleObjects()与WaitForSingleObject()相似,同时监视位于句柄数组中的全部句柄。这些被监视对象的句柄享有平等的优先权,任何一个句柄都不可能比其余句柄具备更高的优先权。WaitForMultipleObjects()的函数原型为:
DWORD WaitForMultipleObjects(
DWORD nCount, // 等待句柄数
CONST HANDLE *lpHandles, // 句柄数组首地址
BOOL fWaitAll, // 等待标志
DWORD dwMilliseconds // 等待时间间隔
);
参数nCount指定了要等待的内核对象的数目,存放这些内核对象的数组由lpHandles来指向。fWaitAll对指定的这nCount个内核对象的两种等待方式进行了指定,为TRUE时当全部对象都被通知时函数才会返回,为FALSE则只要其中任何一个获得通知就能够返回。dwMilliseconds在这里的做用与在WaitForSingleObject()中的做用是彻底一致的。若是等待超时,函数将返回WAIT_TIMEOUT。若是返回WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1中的某个值,则说明全部指定对象的状态均为已通知状态(当fWaitAll为TRUE时)或是用以减去WAIT_OBJECT_0而获得发生通知的对象的索引(当fWaitAll为FALSE时)。若是返回值在WAIT_ABANDONED_0与WAIT_ABANDONED_0+nCount-1之间,则表示全部指定对象的状态均为已通知,且其中至少有一个对象是被丢弃的互斥对象(当fWaitAll为TRUE时),或是用以减去WAIT_OBJECT_0表示一个等待正常结束的互斥对象的索引(当fWaitAll为FALSE时)。 下面给出的代码主要展现了对WaitForMultipleObjects()函数的使用。经过对两个事件内核对象的等待来控制线程任务的执行与中途退出:
HANDLE hEvents[2]; // 存放事件句柄的数组
UINT ThreadProc14(LPVOID pParam)
{
DWORD dwRet1 = WaitForMultipleObjects(2, hEvents, FALSE, INFINITE); // 等待开启事件
if (dwRet1 == WAIT_OBJECT_0) // 若是开启事件到达则线程开始执行任务
{
AfxMessageBox("线程开始工做!");
while (true)
{
for (int i = 0; i < 10000; i++);
DWORD dwRet2 = WaitForMultipleObjects(2, hEvents, FALSE, 0); // 在任务处理过程当中等待结束事件
if (dwRet2 == WAIT_OBJECT_0 + 1) // 若是结束事件置位则当即终止任务的执行
break;
}
}
AfxMessageBox("线程退出!");
return 0;
}
……
void CSample08View::OnStartEvent()
{
for (int i = 0; i < 2; i++) // 建立线程
hEvents[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
AfxBeginThread(ThreadProc14, NULL); // 开启线程
SetEvent(hEvents[0]); // 设置事件0(开启事件)
}
void CSample08View::OnEndevent()
{
SetEvent(hEvents[1]); // 设置事件1(结束事件)
}
MFC为事件相关处理也提供了一个CEvent类,共包含有除构造函数外的4个成员函数PulseEvent()、ResetEvent()、SetEvent()和UnLock()。在功能上分别至关与Win32 API的PulseEvent()、ResetEvent()、SetEvent()和CloseHandle()等函数。而构造函数则履行了原CreateEvent()函数建立事件对象的职责,其函数原型为:
CEvent(BOOL bInitiallyOwn = FALSE, BOOL bManualReset = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
3.信号量内核对象
信号量(Semaphore)内核对象对线程的同步方式与前面几种方法不一样,它容许多个线程在同一时刻访问同一资源,可是须要限制在同一时刻访问此资源的最大线程数目。在用CreateSemaphore()建立信号量时即要同时指出容许的最大资源计数和当前可用资源计数。通常是将当前可用资源计数设置为最大资源计数,每增长一个线程对共享资源的访问,当前可用资源计数就会减1,只要当前可用资源计数是大于0的,就能够发出信号量信号。可是当前可用计数减少到0时则说明当前占用资源的线程数已经达到了所容许的最大数目,不能在容许其余线程的进入,此时的信号量信号将没法发出。线程在处理完共享资源后,应在离开的同时经过ReleaseSemaphore()函数将当前可用资源计数加1。在任什么时候候当前可用资源计数决不可能大于最大资源计数。
使用信号量内核对象进行线程同步主要会用到CreateSemaphore()、OpenSemaphore()、ReleaseSemaphore()、WaitForSingleObject()和WaitForMultipleObjects()等函数。其中,CreateSemaphore()用来建立一个信号量内核对象,其函数原型为:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, // 安全属性指针
LONG lInitialCount, // 初始计数
LONG lMaximumCount, // 最大计数
LPCTSTR lpName // 对象名指针
);
参数lMaximumCount是一个有符号32位值,定义了容许的最大资源计数,最大取值不能超过4294967295。lpName参数能够为建立的信号量定义一个名字,因为其建立的是一个内核对象,所以在其余进程中能够经过该名字而获得此信号量。OpenSemaphore()函数便可用来根据信号量名打开在其余进程中建立的信号量,函数原型以下:
HANDLE OpenSemaphore(
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 信号量名
);
在线程离开对共享资源的处理时,必须经过ReleaseSemaphore()来增长当前可用资源计数。不然将会出现当前正在处理共享资源的实际线程数并无达到要限制的数值,而其余线程却由于当前可用资源计数为0而仍没法进入的状况。ReleaseSemaphore()的函数原型为:
BOOL ReleaseSemaphore(
HANDLE hSemaphore, // 信号量句柄
LONG lReleaseCount, // 计数递增数量
LPLONG lpPreviousCount // 先前计数
);
该函数将lReleaseCount中的值添加给信号量的当前资源计数,通常将lReleaseCount设置为1,若是须要也能够设置其余的值。WaitForSingleObject()和WaitForMultipleObjects()主要用在试图进入共享资源的线程函数入口处,主要用来判断信号量的当前可用资源计数是否容许本线程的进入。只有在当前可用资源计数值大于0时,被监视的信号量内核对象才会获得通知。
信号量的使用特色使其更适用于对Socket(套接字)程序中线程的同步。例如,网络上的HTTP服务器要对同一时间内访问同一页面的用户数加以限制,这时能够为没一个用户对服务器的页面请求设置一个线程,而页面则是待保护的共享资源,经过使用信号量对线程的同步做用能够确保在任一时刻不管有多少用户对某一页面进行访问,只有不大于设定的最大用户数目的线程可以进行访问,而其余的访问企图则被挂起,只有在有用户退出对此页面的访问后才有可能进入。下面给出的示例代码即展现了相似的处理过程:
HANDLE hSemaphore; // 信号量对象句柄
UINT ThreadProc15(LPVOID pParam)
{
WaitForSingleObject(hSemaphore, INFINITE); // 试图进入信号量关口
AfxMessageBox("线程一正在执行!"); // 线程任务处理
ReleaseSemaphore(hSemaphore, 1, NULL); // 释放信号量计数
return 0;
}
UINT ThreadProc16(LPVOID pParam)
{
WaitForSingleObject(hSemaphore, INFINITE);
AfxMessageBox("线程二正在执行!");
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
UINT ThreadProc17(LPVOID pParam)
{
WaitForSingleObject(hSemaphore, INFINITE);
AfxMessageBox("线程三正在执行!");
ReleaseSemaphore(hSemaphore, 1, NULL);
return 0;
}
……
void CSample08View::OnSemaphore()
{
hSemaphore = CreateSemaphore(NULL, 2, 2, NULL); // 建立信号量对象
AfxBeginThread(ThreadProc15, NULL); // 开启线程
AfxBeginThread(ThreadProc16, NULL);
AfxBeginThread(ThreadProc17, NULL);
}
在MFC中,经过CSemaphore类对信号量做了表述。该类只具备一个构造函数,能够构造一个信号量对象,并对初始资源计数、最大资源计数、对象名和安全属性等进行初始化,其原型以下:
CSemaphore( LONG lInitialCount = 1, LONG lMaxCount = 1, LPCTSTR pstrName = NULL, LPSECURITY_ATTRIBUTES lpsaAttributes = NULL );
在构造了CSemaphore类对象后,任何一个访问受保护共享资源的线程都必须经过CSemaphore从父类CSyncObject类继承获得的Lock()和UnLock()成员函数来访问或释放CSemaphore对象。与前面介绍的几种经过MFC类保持线程同步的方法相似,经过CSemaphore类也能够将前面的线程同步代码进行改写,这两种使用信号量的线程同步方法不管是在实现原理上仍是从实现结果上都是彻底一致的。下面给出经MFC改写后的信号量线程同步代码:
// MFC信号量类对象
CSemaphore g_clsSemaphore(2, 2);
UINT ThreadProc24(LPVOID pParam)
{
// 试图进入信号量关口
g_clsSemaphore.Lock();
// 线程任务处理
AfxMessageBox("线程一正在执行!");
// 释放信号量计数
g_clsSemaphore.Unlock();
return 0;
}
UINT ThreadProc25(LPVOID pParam)
{
// 试图进入信号量关口
g_clsSemaphore.Lock();
// 线程任务处理
AfxMessageBox("线程二正在执行!");
// 释放信号量计数
g_clsSemaphore.Unlock();
return 0;
}
UINT ThreadProc26(LPVOID pParam)
{
// 试图进入信号量关口
g_clsSemaphore.Lock();
// 线程任务处理
AfxMessageBox("线程三正在执行!");
// 释放信号量计数
g_clsSemaphore.Unlock();
return 0;
}
……
void CSample08View::OnSemaphoreMfc()
{
// 开启线程
AfxBeginThread(ThreadProc24, NULL);
AfxBeginThread(ThreadProc25, NULL);
AfxBeginThread(ThreadProc26, NULL);
}
4.互斥内核对象
互斥(Mutex)是一种用途很是普遍的内核对象。可以保证多个线程对同一共享资源的互斥访问。同临界区有些相似,只有拥有互斥对象的线程才具备访问资源的权限,因为互斥对象只有一个,所以就决定了任何状况下此共享资源都不会同时被多个线程所访问。当前占据资源的线程在任务处理完后应将拥有的互斥对象交出,以便其余线程在得到后得以访问资源。与其余几种内核对象不一样,互斥对象在操做系统中拥有特殊代码,并由操做系统来管理,操做系统甚至还容许其进行一些其余内核对象所不能进行的很是规操做。
以互斥内核对象来保持线程同步可能用到的函数主要有CreateMutex()、OpenMutex()、ReleaseMutex()、WaitForSingleObject()和WaitForMultipleObjects()等。在使用互斥对象前,首先要经过CreateMutex()或OpenMutex()建立或打开一个互斥对象。CreateMutex()函数原型为:
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
BOOL bInitialOwner, // 初始拥有者
LPCTSTR lpName // 互斥对象名
);
参数bInitialOwner主要用来控制互斥对象的初始状态。通常多将其设置为FALSE,以代表互斥对象在建立时并无为任何线程所占有。若是在建立互斥对象时指定了对象名,那么能够在本进程其余地方或是在其余进程经过OpenMutex()函数获得此互斥对象的句柄。OpenMutex()函数原型为:
HANDLE OpenMutex(
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 互斥对象名
);
当目前对资源具备访问权的线程再也不须要访问此资源而要离开时,必须经过ReleaseMutex()函数来释放其拥有的互斥对象,其函数原型为:
BOOL ReleaseMutex(HANDLE hMutex);
其惟一的参数hMutex为待释放的互斥对象句柄。至于WaitForSingleObject()和WaitForMultipleObjects()等待函数在互斥对象保持线程同步中所起的做用与在其余内核对象中的做用是基本一致的,也是等待互斥内核对象的通知。可是这里须要特别指出的是:在互斥对象通知引发调用等待函数返回时,等待函数的返回值再也不是一般的WAIT_OBJECT_0(对于WaitForSingleObject()函数)或是在WAIT_OBJECT_0到WAIT_OBJECT_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数),而是将返回一个WAIT_ABANDONED_0(对于WaitForSingleObject()函数)或是在WAIT_ABANDONED_0到WAIT_ABANDONED_0+nCount-1之间的一个值(对于WaitForMultipleObjects()函数)。以此来代表线程正在等待的互斥对象由另一个线程所拥有,而此线程却在使用完共享资源前就已经终止。除此以外,使用互斥对象的方法在等待线程的可调度性上同使用其余几种内核对象的方法也有所不一样,其余内核对象在没有获得通知时,受调用等待函数的做用,线程将会挂起,同时失去可调度性,而使用互斥的方法却能够在等待的同时仍具备可调度性,这也正是互斥对象所能完成的很是规操做之一。
在编写程序时,互斥对象多用在对那些为多个线程所访问的内存块的保护上,能够确保任何线程在处理此内存块时都对其拥有可靠的独占访问权。下面给出的示例代码即经过互斥内核对象hMutex对共享内存快g_cArray[]进行线程的独占访问保护。下面给出实现代码清单:
// 互斥对象
HANDLE hMutex = NULL;
char g_cArray[10];
UINT ThreadProc18(LPVOID pParam)
{
// 等待互斥对象通知
WaitForSingleObject(hMutex, INFINITE);
// 对共享资源进行写入操做
for (int i = 0; i < 10; i++)
{
g_cArray[i] = 'a';
Sleep(1);
}
// 释放互斥对象
ReleaseMutex(hMutex);
return 0;
}
UINT ThreadProc19(LPVOID pParam)
{
// 等待互斥对象通知
WaitForSingleObject(hMutex, INFINITE);
// 对共享资源进行写入操做
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
// 释放互斥对象
ReleaseMutex(hMutex);
return 0;
}
……
void CSample08View::OnMutex()
{
// 建立互斥对象
hMutex = CreateMutex(NULL, FALSE, NULL);
// 启动线程
AfxBeginThread(ThreadProc18, NULL);
AfxBeginThread(ThreadProc19, NULL);
// 等待计算完毕
Sleep(300);
// 报告计算结果
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
互斥对象在MFC中经过CMutex类进行表述。使用CMutex类的方法很是简单,在构造CMutex类对象的同时能够指明待查询的互斥对象的名字,在构造函数返回后便可访问此互斥变量。CMutex类也是只含有构造函数这惟一的成员函数,当完成对互斥对象保护资源的访问后,可经过调用从父类CSyncObject继承的UnLock()函数完成对互斥对象的释放。CMutex类构造函数原型为:
CMutex( BOOL bInitiallyOwn = FALSE, LPCTSTR lpszName = NULL, LPSECURITY_ATTRIBUTES lpsaAttribute = NULL );
该类的适用范围和实现原理与API方式建立的互斥内核对象是彻底相似的,但要简洁的多,下面给出就是对前面的示例代码经CMutex类改写后的程序实现清单:
CMutex g_clsMutex(FALSE, NULL); // MFC互斥类对象
UINT ThreadProc27(LPVOID pParam)
{
g_clsMutex.Lock(); // 等待互斥对象通知
for (int i = 0; i < 10; i++) // 对共享资源进行写入操做
{
g_cArray[i] = 'a';
Sleep(1);
}
g_clsMutex.Unlock(); // 释放互斥对象
return 0;
}
UINT ThreadProc28(LPVOID pParam)
{
g_clsMutex.Lock();
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
g_clsMutex.Unlock();
return 0;
}
……
void CSample08View::OnMutexMfc()
{
AfxBeginThread(ThreadProc27, NULL);
AfxBeginThread(ThreadProc28, NULL);
Sleep(300);
CString sResult = CString(g_cArray); AfxMessageBox(sResult); }