今天简述下线程同步的2个方法:关键代码段和互斥对象
关键代码段:
什么是关键代码段?
关键代码段是指一个小代码段,在代码能够执行前,它必须独占对某些共享资源的访问权。这是让若干行代码能够“以原子操作方式”来使用资源的一种方法。
用法
VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);
//用于初始化一个PCRITICAL_SECTION变量,如果我们不经初始化就使用的话,会出现不可预知的问题
VOID DeleteCriticalSection(PCRITICAL_SECTION pcs);
//用于删除一个PCRITICAL_SECTION变量
VOID EnterCriticalSection(PCRITICAL_SECTION pcs);
//用于标志进入关键代码段
VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);
//用于表示离开关键代码段
//举例
DWORD WINAPI FirstThread(PVOID pvParam)
{
while(g_nIndex < MAX_TIMES)
{
EnterCriticalSection(&g_cs);
g_dwTimes[g_nIndex] = GetTickCount();//g_dwTimes 和g_nIndex 都被独占
g_nIndex++;
LeaveCriticalSection(&g_cs);
}
return(0);
}
注意事项:
1每个共享资源使用一个C R I T I C A L _ S E C T I O N变量
注:不同的资源并不是总是同时的被同一个线程使用,所以为了保证它们之间互不制约,我们最好给每个共享资源都设置一个C R I T I C A L _ S E C T I O N变量
2同时访问多个资源时必须始终按照完全相同的顺序请求对资源的访问
注:否则会产生死锁,见下列代码
DWORD WINAPI ThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csNums);
EnterCriticalSection(&g_csChars);
//This loop requires simultaneous access to both resources.
for(int x = 0; x < 100; x++)
g_nNums[x] = g_cChars[x];
LeaveCriticalSection(&g_csChars);
LeaveCriticalSection(&g_csNums);
return(0);
}
DWORD WINAPI OtherThreadFunc(PVOID pvParam)
{
EnterCriticalSection(&g_csChars);
EnterCriticalSection(&g_csNums);
for(int x = 0; x < 100; x++)
g_nNums[x] = g_cChars[x];
LeaveCriticalSection(&g_csNums);
LeaveCriticalSection(&g_csChars);
return(0);
}
3不要长时间运行关键代码段,既然是关键当然是少数而重要时候哦
注:我们尽可能的在不影响效率的情况下使用关键代码段,如下代码,我们不希望在sendmsg的时候值被改变。
SOMESTRUCT g_s;
CRITICAL_SECTION g_cs;
//这样可以实现预期目的,但是效率就很低了,其他的线程完全不能访问g_s,这不是我们想要的
DWORD WINAPI SomeThread(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
//Send a message to a window.
SendMessage(hwndSomeWnd, WM_SOMEMSG, &g_s, 0);
LeaveCriticalSection(&g_cs);
return(0);
}
//这样既保证Sendmsg的值不会被改变,也让g_s不被独占
DWORD WINAPI SomeThread(PVOID pvParam)
{
EnterCriticalSection(&g_cs);
SOMESTRUCT sTemp = g_s;
LeaveCriticalSection(&g_cs);
//Send a message to a window.
SendMessage(hwndSomeWnd, WM_SOMEMSG, &sTemp, 0);
return(0);
}
4编写的需要使用共享资源的任何代码都必须封装在E n t e r C r i t i c a l S e c t i o n和L e a v e C r i t i c a l S e c t i o n函数中。
注:这个很难记住,所以写代码时要注意。如果忘记将代码封装在一个位置,共享资源就可能遭到破坏。
互斥对象:
什么是互斥对象
互斥对象(m u t e x)内核对象能够确保线程拥有对单个资源的互斥访问权。实际上互斥对象是因此而得名的。互斥对象包含一个使用数量,一个线程I D和一个递归计数器。互斥对象的行为特性与关键代码段相同,但是互斥对象属于内核对象,而关键代码段则属于用户方式对象。
注:所以互斥对象使用范围可以更大,它可以用于不同的进程。但是它是内核对象所以耗费的时间也要比用户对象的关键代码段要多
用法
//创建互斥对象
HANDLE CreateMutex( PSECURITY_ATTRIBUTES psa, BOOL fInitialOwner, PCTSTR pszName);
//打开一个互斥对象,另一个进程可以获得它自己进程与现有互斥对象相关的句柄
HANDLE OpenMutex( DWORD fdwAccess, BOOL bInheritHandle, PCTSTR pszName);
//释放对该内核对象的使用,不是释放该内核对象
BOOL ReleaseMutex(HANDLE hMutex);
//释放互斥内核对象
ClosedHandle(hmtx);
//具体用法
BOOL CQueue::Append(PELEMENT pElement, DWORD dwTimeout)
{
BOOL fOk = FALSE;
DWORD dw = WaitForSingleObject(m_hmtxQ, dwTimeout);
.
.
.
ReleaseMutex(m_hmtxQ);
return(fOk); // Call GetLastError for more info
}
注意事项:
1如果两个线程I D相同,即使互斥对象处于未通知状态,系统也允许该线程保持可调度状态。我们不认为该“异常”行为特性适用于系统中的任何地方的其他内核对象。每当线程成功地等待互斥对象时,该对象的递归计数器就递增。若要使递归计数器的值大于1,唯一的方法是线程多次等待相同的互斥对象,以便利用这个异常规则。
注:如果线程多次成功地等待一个互斥对象,在互斥对象的递归计数器变成0之前,该线程必须以同样的次数调用R e l e a s e M u t e x函数。
下面有一张图表说明 关键代码段和 互斥对象的异同:
谢谢你的阅读