Interlock系列函数:
操作具有原子性。具体详见《windows核心编程第8章》
Interlock函数用在旋转锁中的情况:
//global variable indicating whether a shared resource is in use or not
BOOL g_fResourceInUse = FALSE; ...
void Func1(){
//wait to access the resource.
while( InterlockedExchange(&g_fResourceInUse, TRUE) == TRUE ){
Sleep(0);
}
//access the resource
...
//We no longer need to access the resource
InterlockedExchange( &g_fResourceInUse, FALSE );
}
while会不停地运行,把g_fResourceInUse的值设置为TRUE并检查原来的值是否为TRUE。如果原来的值为FALSE,那说明资源尚未被使用,于是调用线程立刻就能将它设置为“使用中”,然后退出循环。如果原来的值是TRUE,那说明其他线程正在使用该资源,于是while将继续执行。
条件变量:
让线程以原子方式把锁释放并将自己阻塞。
SleepConditionVariableCS(...) //用于释放关键段,并等待在某个条件变量上。
SleepConditionVariableSRW(...) //用于释放读写锁,并等待在某个条件变量上。
当条件满足的时候,可以调用Wake函数将沉睡的等待唤醒:
WakeConditionVariable(...)
//唤醒一个等待的线程,让它得到锁,并返回。当这个线程释放同一个锁的时候,不会唤醒其他正在等待同一个条件变量的线程。
WakeAllConditionVariable(...) //唤醒所有等待的线程
停止线程时的死锁问题:
void StopProcessing(){
if(!g_fShutdown){
InterlockedExchangePointer((PLONG*)&g_fShutdown, (LONG)TRUE);
//Free all threads waiting on condition variables
WakeAllConditionVariable(&g_cvReadyToConsume);
WakeAllConditionVaraible(*g_cvReadyToProduce);
//Wait for all the threads to terminate & then clean up.
WaitForMultipleObjects(g_nNumThreads, g_hThreads, TRUE, INFINITE);
//Don't forget to clean up kernal resources
//Note: This is not really mandatory since the process is exiting.
while(g_nNumThreads--){
CloseHandle(g_hThreads[g_nNumThreads]);
//Close each list box
AddText(GetDlgItem(g_hwnd, IDC_SERVERS), TEXT("--------------"));
AddText(GetDlgItem(g_hwnd, IDC_CLIENTS), TEXT("--------------"));
}
}
}
代码先把
g_fShutdown标志设为TURE,接着调用
WakeAllConditionVariable来触发两个条件变量,然后调用
WaitForMultipleObjects,把所有正在运行的线程的句柄在一个数组参数内传入。当
WaitForMultipleObjects返回时,所有线程的句柄都已关闭,代码把两个列表框的最后一行添加到列表框中。
在客户/服务器这边,一旦因为WakeAllConditionVariable而把它们从SleepConditionVariableSRW调用唤醒,照道理这些线程应该会监视
g_fShutdown标志并在各自的列表框中显示"bye-bye"后直接退出。死锁正是发生在当线程向列表框发送消息的时候。如果执行StopProcessing函数的代码位于WM_COMMAND消息处理函数的内部,那么负责处理消息的用户界面线程会阻塞在
WaitForMultipleObjects函数中。因此,当其中一个客户线程或服务器线程在调用ListBox_SetCurSel和ListBox_AddString的时候——死锁了。
所以千万不要让界面线程阻塞,然后还在往界面上发送消息。
一些有用的窍门和技巧:
1. 以原子方式操作一组对象时,使用一个锁。
2. 同时访问多个逻辑资源(例如我们要访问两个或者更多个资源的时候),这些资源如果都分别有自己的锁,那么获取这些锁的顺序一定要一致。否则会出现A线程拥有1号资源等2号资源,而B线程拥有2号资源等待1号资源的死锁情况。
3. 不要长时间占用锁。
下面的代码会在WM_SOMEMSG消息被发送到另一个窗口之前,需要阻止其他线程修改g_s的值。
SOMESTRUCT g_s;
CRITICAL_SECTION g_cs;
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;
}
我们不能知道窗口过程需要多长时间来处理WM_SOMEMSG消息。可能几毫秒,也有可能几年。所以下面的方法更好些。
SOMESTRUCT g_s;
CRITICAL_SECTION g_cs;
DWORD WINAPI SomeThread(PVOID pvParam){
EnterCriticalSection(&g_cs);
SOMESTRUCT sTmp = g_s;
LeaveCriticalSection(&g_cs);
//send a message to a window.
SendMessage(hWndSomeWnd, WM_SOMEMSG, &
sTmp
, 0);
return 0;
}
以上代码可能保证我们尽量少的时间占用锁。