MFC提供了多种同步对象,下面我们只介绍最常用的四种:
1、临界区(CCriticalSection)
2、事件(CEvent)
3、互斥量(CMutex)
4、信号量(CSemaphore)
一、临界区
使用CCriticalSection类。是一个用于同步的对象,同一时刻只允许一个线程存取资源或代码区。临界区在控制一次只有一个线程修改数据或其它的控制资源时非常有用。
class CCriticalSection : public CSyncObject
BOOL Lock();
BOOL Lock(DWORD dwTimeout);//忽略此参数值
//调用此成员函数,获取对关键节对象的访问权限。
//返回值:如果函数成功,则不为零;否则为 0
BOOL Unlock();
//释放对象 CCriticalSection 供其他线程使用。
//返回值:如果对象由线程拥有且发布成功,则不为零;否则为 0。
示例:
临界是一个公共的资源,当有一个线程希望获取(主要是修改)临界资源的时候,应该先向系统申请,先建立一个对象:
CCriticalSection criticalSection;
在需要访问临界的线程前加上资源锁:
criticalSection.Lock();
如果资源没有被其他线程用(锁),就获取资源,否则就阻塞。当这个线程用完资源后,要用:
criticalSection.UnLock();
来解除资源锁定,否则其他以后都不能再访问到临界资源。
CCriticalSection criticalSection; // MFC临界区类对象
char g_cArray[10]; // 共享资源
UINT ThreadProc20(LPVOID pParam)
{
criticalSection.Lock(); // 进入临界区
for (int i = 0; i < 10; i++) // 对共享资源进行写入操作
{
g_cArray[i] = 'a';
Sleep(1);
}
criticalSection.Unlock(); // 离开临界区
return 0;
}
UINT ThreadProc21(LPVOID pParam)
{
criticalSection.Lock();
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
criticalSection.Unlock();
return 0;
}
使用CRITICAL_SECTION结构体对象保护共享资源。
typedef struct _RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
//
// The following three fields control entering and exiting the critical
// section for the resource
//
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;// from the thread's ClientId->UniqueThread
HANDLE LockSemaphore;
ULONG_PTR SpinCount;// force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;
typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
CRITICAL_SECTION m_cs;
InitializeCriticalSection(&m_cs);//初始化临界区
EnterCriticalSection(&m_cs);//进入临界区
LeaveCriticalSection(&m_cs);//离开临界区
CRITICAL_SECTION m_cs; // 临界区结构对象
char g_cArray[10]; // 共享资源
UINT ThreadProc10(LPVOID pParam)
{
EnterCriticalSection(&m_cs); // 进入临界区
for (int i = 0; i < 10; i++) // 对共享资源进行写入操作
{
g_cArray[i] = 'a';
Sleep(1);
}
LeaveCriticalSection(&m_cs); // 离开临界区
return 0;
}
UINT ThreadProc11(LPVOID pParam)
{
EnterCriticalSection(&m_cs);
for (int i = 0; i < 10; i++)
{
g_cArray[10 - i - 1] = 'b';
Sleep(1);
}
LeaveCriticalSection(&m_cs);
return 0;
}
……
void CSample08View::OnCriticalSection()
{
InitializeCriticalSection(&m_cs); // 初始化临界区
AfxBeginThread(ThreadProc10, NULL); // 启动线程
AfxBeginThread(ThreadProc11, NULL);
Sleep(300);
CString sResult = CString(g_cArray);
AfxMessageBox(sResult);
}
二、事件
使用CEvent类。
CEvent(
BOOL bInitiallyOwn = FALSE,//FALSE:所有想要访问资源的线程都必须等待。
BOOL bManualReset = FALSE,//如果为 TRUE,则指定事件对象是手动事件,否则事件对象是自动事件。
LPCTSTR lpszName = NULL,//CEvent 对象的名称。
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL//事件对象的安全属性。
);
等待,直到一个或所有指定的对象处于信号状态或超时间隔过去。
DWORD WaitForMultipleObjects(
DWORD nCount, // 等待句柄数
CONST HANDLE *lpHandles, // 句柄数组首地址
BOOL fWaitAll, // 等待标志
DWORD dwMilliseconds // 等待时间间隔
);
等待指定的对象处于信号状态或超时间隔过去。
DWORD WaitForSingleObject(
[in] HANDLE hHandle,//对象的句柄
[in] DWORD dwMilliseconds//超时间隔,以毫秒为单位
);
示例:
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(结束事件)
}
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);
}
三、互斥量
使用CMutex类。
CMutex(BOOL bInitiallyOwn = FALSE,
//指定创建对象的线程 CMutex 最初是否可以访问由 mutex 控制的资源。
LPCTSTR lpszName = NULL, //CMutex 对象的名称。
LPSECURITY_ATTRIBUTES lpsaAttribute = NULL//Mutex 对象的安全特性。
);
HANDLE CreateMutex(
LPSECURITY_ATTRIBUTES lpMutexAttributes, // 安全属性指针
BOOL bInitialOwner, // 初始拥有者
LPCTSTR lpName // 互斥对象名
);
HANDLE OpenMutex(
DWORD dwDesiredAccess, // 访问标志
BOOL bInheritHandle, // 继承标志
LPCTSTR lpName // 互斥对象名
);
BOOL ReleaseMutex(HANDLE hMutex);
// 互斥对象
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);
}
四、信号量
使用CSemaphore类。类的对象 CSemaphore表示一个 "信号量",一种同步对象,允许一个或多个进程中有限数量的线程访问指定资源的线程数。
CSemaphore(
LONG lInitialCount = 1,//信号量的初始使用计数。
LONG lMaxCount = 1,//信号量的最大使用计数。 必须大于 0。
LPCTSTR pstrName = NULL,//信号灯的名称。
LPSECURITY_ATTRIBUTES lpsaAttributes = NULL//信号灯对象的安全特性。
);
HANDLE CreateSemaphore(
LPSECURITY ATTRIBUTES lpSemaphoreAttributes, //安全属性
LONG lInitialCount, //信号量对象的初始值
LONG lMaximumCount, //信号量的最大值
LPCTSTR lpName //信号量名
);
//返回值:信号量创建成功,将返回该信号量的句柄。
//如果给出的信号量名是系统已经存在的信号量,将返回这个已存在信号量的句柄。
//如果失败,系统返回NULL,可以调用函数GetLastError()查询失败的原因。
HANDLE OpenSemaphore(
DWORD dwDesiredAccess,//表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。
BOOL bInheritHandle,//表示信号量句柄继承性,一般传入TRUE即可。
LPCTSTR lpName//表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。
);
BOOL ReleaseSemaphore(
HANDLE hSemaphore,//信号量的句柄。
LONG lReleaseCount,//表示增加个数,必须大于0且不超过最大资源数量。
LPLONG lpPreviousCount//可以用来传出先前的资源计数,设为NULL表示不需要传出。
);
#include <stdio.h>
#include <process.h>
#include <windows.h>
long g_nNum;
unsigned int __stdcall Fun(void *pPM);
const int THREAD_NUM = 10;
//信号量与关键段
HANDLE g_hThreadParameter;//用信号量处理主线程与子线程的同步
CRITICAL_SECTION g_csThreadCode;//关键段来处理各子线程间的互斥.对公有资源进行互斥访问,公有资源g_nNum
int main()
{
//初始化信号量和关键段
g_hThreadParameter = CreateSemaphore(NULL, 0, 1, NULL);//第二个参数表示初始资源数量。当前0个资源,最大允许1个同时访问
InitializeCriticalSection(&g_csThreadCode);
HANDLE handle[THREAD_NUM];
g_nNum = 0;
int i = 0;
while (i < THREAD_NUM)
{
handle[i] = (HANDLE)_beginthreadex(NULL, 0, Fun, &i, 0, NULL);
Sleep(1);
WaitForSingleObject(g_hThreadParameter, INFINITE);
//依次使线程进入等待状态(阻塞队列)。
++i;
}
WaitForMultipleObjects(THREAD_NUM, handle, TRUE, INFINITE);
DeleteCriticalSection(&g_csThreadCode);
//销毁信号量和关键段
CloseHandle(g_hThreadParameter);
for (i = 0; i < THREAD_NUM; i++)
CloseHandle(handle[i]);
getchar();
return 0;
}
unsigned int __stdcall Fun(void *pPM)
{
int nThreadNum = *(int *)pPM;
ReleaseSemaphore(g_hThreadParameter, 1, NULL);
//信号量的当前资源+1,第二个参数表示增加个数,必须大于0且不超过最大资源数量。
Sleep(50);//some work should to do
EnterCriticalSection(&g_csThreadCode);
++g_nNum;
Sleep(10);//some work should to do
printf("线程编号为%d 全局资源值为%d\n", nThreadNum, g_nNum);
LeaveCriticalSection(&g_csThreadCode);
return 0;
}