《Win32多线程程序设计》–Jim Beveridge & Robert Wiener
同步(synchronous):当程序1调用程序2时,程序1 停下不动,直到程序2完成回到程序1来,程序1才继续下去;
- SendMessage() 根本就像是“直接调用窗口之窗口函数”,除非等该窗口函数结束,是不会回到原调用点的,所以它是同步行为。
异步(asynchronous):如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是asynchronous;
- PostMessage() 是把消息放到对方的消息队列中,然后不管三七二十一,就回到原调用点继续执行,所以这是异步行为。
- Win32 中关于进程和线程的协调工作是由同步机制(synchronous mechanism)来完成的。
- 在任何关于同步机制的讨论中,不论是在 Win32 或 Unix 或其他操作系统,你一定会一再地听到这样一条规则:不要长时间锁住一份资源。最牢靠而最立即的警告:千万不要在一个 critical section 之中调用 Sleep() 或任何 Wait…() API 函数。
某些人会关心这样的问题:如果我再也不释放资源(或不离开 critical section,或不释放 mutex……等等),会怎样?答案是:不会怎样!操作系统不会当掉。用户不会获得任何错误信息。最坏的情况是,当主线程(一个 GUI 线程)需要使用这被锁定的资源时,程序会挂在那儿,动也不动。
1. 关键区域、临界区域(Critical Sections)
Critical section 并不是核心对象。 因此,没有所谓 handle 这样的东西。它和核心对象不同,它存在于进程的内存空间中。
一旦线程进入一个 critical section,它就能够一再地重复进入该 critical section。唯一的警告就是,每一个“进入”操作都必须有一个对应的“离开”操 作 。 如果某个线程调用EnterCriticalSection() 5 次,它也必须调用LeaveCriticalSection() 5 次,该 critical section 才能够被释放。
Critical section 的一个缺点就是,没有办法获知进入 critical section 中的那个线程是生是死。从另一个角度看,由于 critical section 不是核心对象,如果进入 critical section 的那个线程结束了或当掉了,而没有调用LeaveCriticalSection() 的话,系统没有办法将该 critical section 清除。如果你需要那样的机能,你应该使用 mutex。
任何时候当一段代码需要两个(或更多)资源时,都有潜在性的死锁阴影。强迫将资源锁定,使它们成为 ” all-or-nothing”(要不统统获得,要不统统没有),可以阻止死锁的发生,这样做往往会影响程序性能。更好的方式是使用下文的Mutex和WaitForMultipleObjects解决死锁问题。
#include <stdio.h>
#include <Windows.h>
#define LOOPNUM 10000000
typedef struct tagSAFTDATA {
int sum;
CRITICAL_SECTION lock;
} SAFTDATA;
static SAFTDATA g_SaftData = {
0};
static void AddSafety() {
EnterCriticalSection(&g_SaftData.lock);
g_SaftData.sum += 1;
LeaveCriticalSection(&g_SaftData.lock);
}
static void LessSafety() {
EnterCriticalSection(&g_SaftData.lock);
g_SaftData.sum -= 1;
LeaveCriticalSection(&g_SaftData.lock);
}
DWORD WINAPI Thread(void *arg) {
for (int i = 0; i < LOOPNUM; i++) {
EnterCriticalSection(&g_SaftData.lock);
LessSafety();
g_SaftData.sum -= 1;
LeaveCriticalSection(&g_SaftData.lock);
}
return 0;
}
int main(void) {
DWORD start = GetTickCount();
InitializeCriticalSection(&g_SaftData.lock);
HANDLE hThread = CreateThread(NULL, 0, Thread, NULL, 0, NULL);
for (int i = 0; i < LOOPNUM; i++) {
AddSafety();
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
DeleteCriticalSection(&g_SaftData.lock);
DWORD end = GetTickCount();
printf("%d main, usedtime=%d\n", g_SaftData.sum, end-start);
return 0;
}
2. 互斥器(Mutexes)
虽然 mutex 和 critical section 做相同的事情,但是它们的运作还是有差别的:
- 锁住一个未被拥有的 mutex,比锁住一个未被拥有的 critical section,需要花费几乎 100 倍的时间。因为 critical section 不需要进入操作系统核心。
- Mutexes 可以跨进程使用。 Critical section 则只能够在同一个进程中使用。为了能够跨进程使用同一个 mutex,你可以在产生mutex 时指定其名称。如果你指定了名称,系统中的其他任何线程就可以使用这个名称来处理该 mutex。
Mutexes行为特征:
- Mutex 是一个核心对象,因此它被保持在系统核心之中,并且和其他核心对象一样,有所谓的引用计数(reference count)。每次你调用CloseHandle(),引用计数便减 1。当引用计数达到 0 时, mutex 便自动被系统清除掉。如果拥有某 mutex 之线程结束了,该 mutex 会被自动清除的唯一情况是:此线程是最后一个与该 mutex handle 有关联的线程。 否则此核心对象的引用计数仍然是比 0 大,其他线程(以及进程)仍然可以拥有此 mutex 的合法handle。
- 一旦没有任何线程拥有 mutex 且有一个线程正以 Wait…() 等待该 mutex,该 mutex 就会短暂地出现激发状态,使 Wait…() 得以返回。
- 第二个容易引人迷惑的地方是,Mutex 的拥有权并非属于那个产生它的线程,而是那个最后对此 mutex 进行 Wait…() 操作并且尚未进行 ReleaseMutex() 操作的线程。线程拥有 mutex 就好像线程进入 critical section 一样。一次只能有一个线程拥有该 mutex。
- 有时候,线程可能没有在结束前调用ReleaseMutex()。为了解决这个问题,mutex 有一个在各种同步机制中是独一无二的特性:如果线程拥有一个 mutex 而在结束前没有调用ReleaseMutex(),mutex 不会被摧毁。取而代之的是,该 mutex 会被视为“ 未被拥有”以及“未被激发”,而下一个等待中的线程会被以WAIT_ABANDONED_0 通知