Win 32 多线程程序设计学习笔记之四:同步控制(Synchronization)

来源:互联网 发布:数据库默认值设置 编辑:程序博客网 时间:2024/05/20 17:26

        让我先对同步(synchronous)与异步(asynchronous)做个说明。当程序1调用程序2时,程序1停下不动,直到程序2完成回到程序1来,程序1才继续下去,这就是所谓的“synchronous”。如果程序1调用程序2后,径自继续自己的下一个动作,那么两者之间就是所谓的“asynchronous”。Win32 API中的 SendMessage() 就是同步行为,而 PostMessage() 就是异步行为。如下图:

        在 Windows 系统中,PostMessage() 是把消息放到对方的消息队列中,然后不管三七二十一,就回到原调用点继续执行,所以这是异步(asynchronous)行为。而 SendMessage() 根本就像是“直接调用窗口之窗口函数”,除非等该窗口函数结束,是不会回到原调用点的,所以它是同步(synchronous)行为。

        Win32 中关于进程和线程的协调工作是由同步机制( synchronousmechanism)来完成的。同步机制相当于线程之间的红绿灯。你可以设计让一组线程使用同一个红绿灯系统。这个红绿灯系统负责给某个线程绿灯而给其他线程红灯。这一组红绿灯系统必须确保每一个线程都有机会获得绿灯。


1.Critical Sections(关键区域、临界区域)

        Win32 之中最容易使用的一个同步机制就是 critical sections。所谓critical sections 意指一小块“用来处理一份被共享之资源”的程序代码。这里
所谓的资源,并不是指来自 .RES(资源文件)的 Windows 资源,而是广义地指一块内存、一个数据结构、一个文件,或任何其他具有“使用之排他性”的东西。也就是说,“资源”每一次(同一时间内)只能够被一个线程处理。你可能必须在程序的许多地方处理这一块可共享的资源。所有这些程序代码可以被同一个 critical section 保护起来。为了阻止问题发生,一次只能有一个线程获准进入 critical section 中(相对地也就是说资源受到了保护)。实施的方式是在程序中加上“进入”或“离开”critical section 的操作。如果有一个线程已经“进入”某个 critical section,另一个线程就绝对不能够进入同一个 critical section。

        在 Win32 程序中你可以为每一个需要保护的资源声明一个CRITICAL_SECTION 类型的变量。这个变量扮演红绿灯的角色,让同一时间内只有一个线程进入 critical section。Critical section 并不是核心对象。因此,没有所谓 handle 这样的东西。它和核心对象不同,它存在于进程的内存空间中。你不需要使用像“Create”这样的 API 函数获得一个 critical section handle。你应该做的是将一个类型为CRITICAL_SECTION 的局部变量初始化, 方法是调用InitializeCriticalSection():

VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数
lpCriticalSection 一个指针, 指向欲被初始化的
CRITICAL_SECTION 变量。这个变量应该在你的

程序中定义。


返回值
此函数传回 void。

        当你用毕 critical section 时,你必须调用 DeleteCriticalSection() 清除它。这个函数并没有“释放对象”的意义在里头,不要把它和 C++ 的 delete 运算符混淆了。

VOID DeleteCriticalSection(LPCRITICAL_SECTION lpCriticalSection);
参数

lpCriticalSection 指向一个不再需要的 CRITICAL_SECTION 变量。


返回值

此函数传回 void。

        一旦 critical section 被初始化,每一个线程就可以进入其中——只要它通过了 EnterCriticalSection() 这一关。

VOID EnterCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

参数

lpCriticalSection 指向一个你即将锁定的 CRITICAL_SECTION 变量。


返回值
此函数传回 void

        当线程准备好要离开 critical section 时,它必须调用LeaveCriticalSection():

VOID LeaveCriticalSection(LPCRITICAL_SECTION lpCriticalSection);

参数

lpCriticalSection 指向一个你即将解除锁定的 CRITICAL_SECTION 变量。


返回值
此函数传回 void。

        你应该在每一个存取全局数据的地方使用 Enter/Leave 函数。有时候 Enter/Leave 甚至会在同一个函数中出现数次——如果这个函数需
要很长的运行时间。

        Win32 critical section 的一个性质。一旦线程进入一个 critical section,它就能够一再地重复进入该 critical section。这也就是为什么 Insert() 可以调用 AddHead() 而不需先调用 LeaveCriticalSection()的缘故。唯一的警告就是,每一个“进入”操作都必须有一个对应的“离开”操作。如果某个线程调用 EnterCriticalSection() 5 次, 它也必须调用LeaveCriticalSection() 5 次,该 critical section 才能够被释放。


最小锁定时间
在任何关于同步机制的讨论中,不论是在 Win32 或 Unix 或其他操作系统,你一定会一再地听到这样一条规则:
不要长时间锁住一份资源

        千万不要在一个 critical section 之中调用 Sleep() 或任何 Wait...() API 函数。

        Critical section 的一个缺点就是,没有办法获知进入 critical section 中的那个线程是生是死。从另一个角度看,由于 critical section 不是核心对象,如果进入 critical section 的那个线程结束了或当掉了, 而没有调用LeaveCriticalSection() 的话,系统没有办法将该 critical section 清除。如果你需要那样的机能,你应该使用 mutex.

        任何时候当一段代码需要两个(或更多)资源时,都有潜在性的死锁阴影。死锁的情况可能非常复杂,许多线程的独立性彼此纠缠在一起。虽然有一些算法可以侦测并仲裁死锁状态,基本上它们仍嫌过于复杂。对大部分程序而言,最好的政策就是找出一种方法以确保死锁不会发生。稍后你会看到,强迫将资源锁定,使它们成为 "all-or-nothing"(要不统统获得,要不统统没有),可以阻止死锁的发生。


2.互斥器(Mutexes)

        Win32 的 Mutex 用途和 critical section 非常类似,但是它牺牲速度以增加弹性。或许你已经猜到了,mutex 是 MUTual EXclusion 的缩写。一个时间内只能够有一个线程拥有 mutex,就好像同一时间内只能够有一个线程进入同一个 critical section 一样。
        虽然 mutex 和 critical section 做相同的事情,但是它们的运作还是有差别的:

  •          锁住一个未被拥有的 mutex,比锁住一个未被拥有的 critical section,需要花费几乎 100 倍的时间。因为 critical section 不需要进入操作系统核心,直接在“user mode”就可以进行操作。
  •          Mutexes 可以跨进程使用。Critical section 则只能够在同一个进程中使用。
  •         等待一个 mutex 时,你可以指定“结束等待”的时间长度。但对于critical section 则不行。

以下是两种对象的相关函数比较:
CRITICAL_SECTION                  Mutex 核心对象
InitializeCriticalSection()        CreateMutex()
                                                 OpenMutex()
EnterCriticalSection()              WaitForSingleObject()
                                                 WaitForMultipleObjects()
MsgWaitForMultipleObjects()
LeaveCriticalSection()              ReleaseMutex()
DeleteCriticalSection()            CloseHandle()


        为了能够跨进程使用同一个 mutex,你可以在产生 mutex 时指定其名称。如果你指定了名称,系统中的其他任何线程就可以使用这个名称来处理该 mutex。一定要使用名称,因为你没有办法把 handle 交给一个执行中的进程。

        记住,其他程序也可能使用这个同步机制,所以 mutex 名称对整个系统而言是全局性的。不要把你的 mutex 对象命名为“Object”或“Mutex”之类,那太过普遍。请使用一些独一无二的名称,如公司名称或应用程序名称等等。

        与critical sections 不同,当你产生一个 mutex 时,你有某些选择空间。Mutex 是一个核心对象,因此它被保持在系统核心之中,并且和其他核心对象一样,有所谓的引用计数(reference count)。虽然 mutex 的机能与 criticalsection 十分类似,但由于 Win32 术语带来的迷惑,mutex 可能不大容易了解。你可以利用 CreateMutex() 产生一个 mutex:

HANDLE CreateMutex(LPSECURITY_ATTRIBUTES lpMutexAttributes,BOOL bInitialOwner,LPCTSTR lpName);

参数
lpMutexAttributes 安全属性。NULL 表示使用默认的属性。这一指定在 Windows 95 中无效。

bInitialOwner 如果你希望“调用 CreateMutex() 的这个线程”拥有产生出来的 mutex,就将此值设为 TRUE。

lpName mutex 的名称(一个字符串)。任何进程或线程都可以根据此名称使用这一 mutex。名称可以是任意字符串,只要不含反斜(backslash,\)即可。


返回值
如果成功,则传回一个 handle,否则传回 NULL。调用 GetLastError() 可以获得更进一步的信息。如果指定的 mutex 名称已经存在,GetLastError() 会传回 ERROR_ALREADY_EXISTS。


        当你不再需要一个 mutex 时,你可以调用 CloseHandle() 将它关闭。和其他核心对象一样,mutex 有一个引用计数(reference count)。每次你调用CloseHandle(),引用计数便减 1。当引用计数达到0 时,mutex 便自动被系统清除掉.

打开一个互斥器(Mutex)

        如果 mutex 已经被产生了,并有一个名称,那么任何其他的进程和线程便可以根据该名称打开那个 mutex(我这里并不考虑安全属性)。
如果你调用 CreateMutex() 并指定一个早已存在的 mutex 名称,Win32会回给你一个 mutex handle,而不会为你产生一个新的 mutex。就像上面所说的,GetLastError() 会传回 ERROR_ALREADY_EXISTS。你也可以使用 OpenMutex() 打开(而非产生)一个原已存在的 mutex。

        这种情况通常是因为,你写了一个 client 进程,并与同一台机器上的 server 进程交谈,而只有 server 进程才应该产生 mutex,因为它保护了 server 所定义的结构体。

锁住一个互斥器(Mutex)

        欲获得一个 mutex 的拥有权,请使用 Win32 的 Wait...() 函数。Wait...()对 mutex 所做的事情和 EnterCriticalSection() 对 critical section 所做的事情差不多,倒是一大堆术语容易把你迷惑了。一旦没有任何线程拥有 mutex,这个 mutex 便处于激发状态。因此,如果没有任何线程拥有那个 mutex,Wait...() 便会成功。反过来说,当线程拥有mutex 时,它便不处于激发状态。如果有某个线程正在等待一个未被激发的mutex,它便将进入“blocking”(阻塞)状态。也就是说,该线程会停止执行,直到 mutex 被其拥有者释放并处于激发状态。


下面是某种情节的发展:
1. 我们有一个 mutex,此时没有任何线程拥有它,也就是说,它处于非激发状态。
2. 某个线程调用 WaitForSingleObject()(或任何其他的 Wait...() 函数),并指定该 mutex handle 为参数。
3. Win32 于是将该 mutex 的拥有权给予这个线程,然后将此 mutex 的状态短暂地设为激发状态,于是 Wait...() 函数返回。
4. Mutex 立刻又被设定为非激发状态,使任何处于等待状态下的其他线程没有办法获得其拥有权。
5. 获得该 mutex 之线程调用 ReleaseMutex(),将 mutex 释放掉。于是循环回到第一场景,周而复始。

        “一旦没有任何线程拥有 mutex,这个 mutex 便处于激发状态”有点背道而驰。基本上,或许更精密地说,所谓的“mutex 激发状态”应该是:当没有任何线程拥有该 mutex 而且有一个线程正以 Wait...() 等待该 mutex,该mutex 就会短暂地出现激发状态,使 Wait...() 得以返回。

ReleaseMutex() 的规格如下:

BOOL ReleaseMutex(HANDLE hMutex);
参数

hMutex 欲释放之 mutex 的 handle。


返回值

如果成功,传回 TRUE。如果失败,传回 FALSE。


        Mutex 的拥有权是第二个容易引人迷惑的地方。Mutex 的拥有权并非属于那个产生它的线程,而是那个最后对此 mutex 进行 Wait...() 操作并且尚未进行 ReleaseMutex() 操作的线程。线程拥有 mutex 就好像线程进入 criticalsection 一样。一次只能有一个线程拥有该 mutex。

        Mutex 的被摧毁和其拥有权没有什么关系。和大部分其他的核心对象一样,mutex 是在其引用计数降为 0 时被操作系统摧毁的。每当线程对此 mutex调用一次 CloseHandle(),或是当线程结束时,mutex 的引用计数即下降 1。

        如果拥有某 mutex 之线程结束了,该 mutex 会被自动清除的唯一情况是:此线程是最后一个与该 mutex handle 有关联的线程。否则此核心对象的引用计数仍然是比 0 大,其他线程(以及进程)仍然可以拥有此 mutex 的合法handle。然而,当线程结束而没有释放某个 mutex 时,有一种特殊的处理方式。

        在一个适当的程序中,线程绝对不应该在它即将结束前还拥有一个mutex,因为这意味着线程没有能够适当地清除其资源。不幸地是,我们并不
身处在一个完美的世界,有时候,因为某种理由,线程可能没有在结束前调用ReleaseMutex()。为了解决这个问题,mutex 有一个非常重要的特性。这性质在各种同步机制中是独一无二的。如果线程拥有一个 mutex 而在结束前没有调用 ReleaseMutex(),mutex 不会被摧毁。取而代之的是,该 mutex 会被视为“ 未被拥有” 以及“ 未被激发” , 而下一个等待中的线程会被以WAIT_ABANDONED_0 通知。不论线程是因为 ExitThread() 而结束,或是因当掉而结束,这种情况都存在。

        如果其他线程正以 WaitForMultipleObjects() 等待此 mutex,该函数也会返回,传回值介于 WAIT_ABANDONED_0 和(WAIT_ABANDONED_0_n +1)之间,其中的 n 是指handle 数组的元素个数。线程可以根据这个值了解到究竟哪一个 mutex 被放弃了。至于 WaitForSingleObject() , 则只是传回WAIT_ABANDONED_0。

        CreateMutex() 的第二个参数 bInitialOwner,允许你指定现行线程(current thread)是否立刻拥有即将产生出来的mutex。乍见之下这个参数或许只是提供一种方便性,但事实上它阻止了一种 race condition 的发生。

        与 critical section 不同,mutexes 可以跨进程使用,以及跨线程使用。Mutex 可以根据其名称而被开启。所以,另一个进程可以完全不需要和产生mutex 的进程打声招呼, 就根据名称开启一个 mutex 。如果没有bInitialOwner,你就必须写下这样的代码:

HANDLE hMutex = CreateMutex(NULL, FALSE, "Sample Name");int result = WaitForSingleObject(hMutex, INFINITE);

        但是这样的安排可能会产生 race condition。如果在 CreateMutex 完成之后,发生了一个 context switch,执行权被切换到另一个线程,那么其他进程就有可能在 mutex 的产生者调用 WaitForSingleObject() 之前,锁住这个mutex 对象。


3.信号量(Semaphores)
           Win32 中的一个 semaphore 可以被锁住最多 n 次,其中 n 是 semaphore被产生时指定的。n 常常被设计用来代表“可以锁住一份资源”的线程个数,不过并非单独一个线程就不能够拥有所有的锁定。

        mutex 是 semaphore 的一种退化。如果你产生一个semaphore 并令最大值为 1,那就是一个 mutex。也因此,mutex 又常被称为binary semaphore。如果某个线程拥有一个 binary semaphore,那么就没有其他线程能够获得其拥有权。在 Win32 中,这两种东西的拥有(ownership)的意义完全不同,所以它们不能够交换使用。semaphores 不像 mutexes,它并没有所谓的“wait abandoned”状态可以被其他线程侦测到。

产生信号量(Semaphore)

HANDLE CreateSemaphore(LPSECURITY_ATTRIBUTES lpAttributes,LONG lInitialCount,LONG lMaximumCount,LPCTSTR lpName);
参数

lpAttributes 安全属性。如果是 NULL 就表示要使用默认属性。Windows 95 忽略这一参数。

lInitialCount semaphore 的初值。必须大于或等于 0,并且小于或等于 lMaximumCount。

lMaximumCount Semaphore 的最大值。这也就是在同一时间内能够锁住 semaphore 之线程的最多个数。

lpName Semaphore 的名称(一个字符串)。任何线程(或进程) 都可以根据这一名称引用到这个semaphore。这个值可以是 NULL,意思是产生 一个没有名字的 semaphore。


返回值
        如果成功就传回一个 handle,否则传回 NULL。不论哪一种情况,GetLastError() 都会传回一个合理的结果。如果指定的 semaphore 名称已经存在,则该函数还是成功的,GetLastError() 会传回 ERROR_ALREADY_EXISTS。

获得锁定        

        每当一个锁定动作成功,semaphore 的现值就会减 1。你可以使用任何一种 Wait...() 函数(例如 WaitForSingleObject())要求锁定一个 semaphore。因此,如果 semaphore 的现值不为 0,Wait...() 函数会立刻返回。这和 mutex 很像,如果没有任何线程拥有 mutex,Wait...() 会立刻返回。

        如果锁定成功,你也不会收到 semaphore 的拥有权。因为可以有一个以上的线程同时锁定一个 semaphore,所以谈 semaphore 的拥有权并没有太多帮助。在 semaphore 身上并没有所谓“独占锁定”这种事情。也因为没有拥有权的观念,一个线程可以反复调用 Wait...() 函数以产生新的锁定。这和 mutex绝不相同:拥有 mutex 的线程不论再调用多少次 Wait...() 函数,也不会被阻塞住。

        一旦 semaphore 的现值降到 0,就表示资源已经耗尽。此时,任何线程如果调用 Wait...() 函数,必然要等待,直到某个锁定被解除为止。

解除锁定(Releasing Locks)

        为了解除锁定,你必须调用 ReleaseSemaphore()。这个函数将 semaphore的现值增加一个定额,通常是 1,并传回 semaphore 的前一个现值。

        ReleaseSemaphore() 和 ReleaseMutex() 旗鼓相当。当你调用WaitForSingleObject() 并获得一个 semaphore 锁定之后,你就需要调用
ReleaseSemaphore()。Semaphore 常常被用来保护固定大小的环状缓冲区(ring buffer)。程序如果要读取环状缓冲区的内容,必须等待 semaphore。线程将数据写入环状缓冲区,写入的数据可能不只一笔,在这种情况下解除锁定时的 semaphore 增额应该等于写入的数据笔数。

BOOL ReleaseSemaphore(HANDLE hSemaphore,LONG lReleaseCount,LPLONG lpPreviousCount);
参数
hSemaphore Semaphore 的 handle。
lReleaseCount Semaphore 现值的增额。该值不可以是负值或 0。

lpPreviousCount 藉此传回 semaphore 原来的现值。


返回值
如果成功,则传回TRUE。否则传回 FALSE。失败时可调用 GetLastError()获得原因。

        ReleaseSemaphore() 对于 semaphore 所造成的现值的增加,绝对不会超过CreateSemaphore() 时所指定的 lMaximumCount。请记住, lpPreviousCount 所传回来的是一个瞬间值。你不可以把lReleaseCount 加上 *lpPreviousCount,就当作是 semaphore 的现值,因为其他线程可能已经改变了 semaphore 的值。

        与 mutex 不同的是,调用 ReleaseSemaphore() 的那个线程,并不一定就得是调用 Wait...() 的那个线程。任何线程都可以在任何时间调用
ReleaseSemaphore(),解除被任何线程锁定的 semaphore。

        CreateSemaphore() 的第二个参数是 lInitialCount , 它的存在理由和CreateMutex() 的 bInitialOwner 参数的存在理由是一样的。如果你把初值设定为 0,你的线程就可以在产生 semaphore 之后进行所有必要的初始化工作。待初始化工作完成后,调用 ReleaseSemaphore() 就可以把现值增加到其最大可能值。

        以环状缓冲区(ring buffer)为例,semaphore 通常被产生时是以 0 为初值,所以任何一个等待中的线程都会停下来。一旦有东西被加到环状缓冲区中,我们就以 ReleaseSemaphore() 增加 semaphore 的值,于是等待中的线程就可以继续进行。


4.事件(Event Objects)

          Win32 中最具弹性的同步机制就属 events 对象了。Event 对象是一种核心对象,它的唯一目的就是成为激发状态或未激发状态。这两种状态全由程序来控制,不会成为 Wait...() 函数的副作用。

        Event 对象之所以有大用途,正是因为它们的状态完全在你掌控之下。Mutexes 和 semaphores 就不一样了, 它们的状态会因为诸如
WaitForSingleObject() 之类的函数调用而变化。所以,你可以精确告诉一个event 对象做什么事,以及什么时候去做。

为了产生一个 event 对象,你必须调用 CreateEvent():

HANDLE CreateEvent(LPSECURITY_ATTRIBUTES lpEventAttributes,BOOL bManualReset,BOOL bInitialState,LPCTSTR lpName);

参数
lpEventAttributes 安全属性。NULL 表示使用默认属性。该属性在Windows 95 中会被忽略。

bManualReset 如为 FALSE,表示这个 event 将在变成激发状态(因而唤醒一个线程)之后,自动重置(reset)为非激发状态。如果是 TRUE,表示不会自动重置,必须靠程序操作(调用 ResetEvent())才能将激发状态的 event 重置为非激发状态。

bInitialState 如为 TRUE,表示这个 event 一开始处于激发状态。如为 FALSE,则表示这个 event 一开始处于非激发状态。

lpName Event 对象的名称。任何线程或进程都可以根据这个文字名称,使用这一 event 对象。


返回值
        如果调用成功,会传回一个 event handle,GetLastError() 会传回 0。如果lpName 所指定的 event 对象已经存在,CreateEvent() 传回的是该 event handle , 而不会产生一个新的。这时候 GetLastError() 会传回ERROR_ALREADY_EXISTS。如果 CreateEvent() 失败,传回的是 NULL,GetLastError() 可以获得更进一步的失败信息。


        SetEvent() 把 event 对象设为激发状态ResetEvent() 把 event 对象设为非激发状态(译注:在此我要提醒读者,"Reset" 的意思是“设定为非激发状态”,而非“重新设定为激发状态”。)
PulseEvent() 如果是一个 Manual Reset Event:把 event 对象设为激发状态,唤醒“所有”等待中的线程,然后 event 恢复为非激发状态。如果是一个 Auto Reset Event:把 event 对象设为激发状态,唤醒“一个”等待中的线程,然后 event 恢复为非激发状态

        如果你选择一个新的 event 类型(若不是“Automatic”就是“Manual”),原有的 event 对象和线程统统会被摧毁,程序重新产生出新的 event 和新的线程。


5.Interlocked Variables

           同步机制的最简单类型是使用 interlocked 函数,对着标准的 32 位变量进行操作。这些函数并没有提供“等待”机能,它们只是保证对某个特定变量的存取操作是“一个一个接顺序来”。

        考虑一下,如果你需要维护一个 32 位计数器的“排他性存取”性质,你该怎么做。你可能会想产生一个 critical section 或一个 mutex,拥有它,然后进行你的操作,然后再释放拥有权。一个 32 位变量的存取操作只需要 2~3 个机器指令,因此上述的准备动作实在是太多了些,几乎呈现两个 order 的倍数。

        类似的 32 位计数器发生在所谓的引用计数(reference counting)身上,例如系统核心对于核心对象之 handle 的处理。基本上当一个核心对象的引用计数降为 0 时,这个对象就应该被摧毁。你可以“要么降低其引用计数值” , “ 要么判断它是否等于 0 ” , 但是没办法两者并行。InterlockedDecrement() 可以双效合一,它先将计数器内容减 1,再将其值与 0做比较,并且传回比较结果。

所谓的 interlocked 函数,共有两个:

InterlockedIncrement()InterlockedDecrement()


这两个函数都只能够和 0 做比较,不能和任何其他数值比较。

LONG InterlockedIncrement(LPLONG lpTarget);LONG InterlockedDecrement(LPLONG lpTarget);
参数

lpTarget 32 位变量的地址。这个变量内容将被递增或递减,结果将与 0 作比较。这个地址必须指向 longword。


返回值

变量值经过运算(加 1 或减 1)后,如果等于 0,传回 0;如果大于 0,传回一个正值;如果小于 0,传回一个负值。


        Interlocked...() 函数的传回值代表计数器和 0 的比较结果。这一点对于实现我们曾经提过的所谓“引用计数”(reference counting)非常重要,因为我们必须知道“引用计数”何时到达 0。如果没有这个比较,问题就回到了原点,你必须在增减操作之前先锁定该计数器,以使增减操作成为一个“不可切割”的操作。

        InterlockedExchange() 可以设定一个新值并传回旧值。就像Increment/Decrement 函数一样,它提供了一个在多线程环境下的安全做法,用
以完成一个很基础的运算操作。

LONG InterlockedExchange(LPLONG lpTarget,LONG lValue);
参数
lpTarget 32 位变量的地址。这个指针必须指向 long word。

lValue 用以取代 lpTarget 所指内容之新值。


返回值

传回先前由 lpTarget 所指之内容。


6.同步机制摘要

Critical Section

        Critical section(临界区)用来实现“排他性占有”。适用范围是单一进程的各线程之间。它是:

  • 一个局部性对象,不是一个核心对象。
  •  快速而有效率。
  • 不能够同时有一个以上的 critical section 被等待。
  • 无法侦测是否已被某个线程放弃。
Mutex
        Mutex 是一个核心对象,可以在不同的线程之间实现“排他性占有”,甚至即使那些线程分属不同进程。它是:
        一个核心对象。
  •  如果拥有 mutex 的那个线程结束,则会产生一个 “abandoned” 错误信息。
  • 可以使用 Wait...() 等待一个 mutex。
  •  可以具名,因此可以被其他进程开启。
  •  只能被拥有它的那个线程释放(released)。
Semaphore
        Semaphore 被用来追踪有限的资源。它是:
  •  一个核心对象。
  •  没有拥有者。
  • 可以具名,因此可以被其他进程开启。
  • 可以被任何一个线程释放(released)。
Event Object
        Event object 通常使用于 overlapped I/O(第6章),或用来设计某些自定义的同步对象。它是:
  • 一个核心对象。
  • 完全在程序掌控之下。
  • 适用于设计新的同步对象。
  • “要求苏醒”的请求并不会被储存起来,可能会遗失掉。
  •  可以具名,因此可以被其他进程开启。

Interlocked Variable
        如果 Interlocked...() 函数被使用于所谓的 spin-lock,那么它们只是一种同步机制。所谓 spin-lock 是一种 busy loop,被预期在极短时间内执行,所以有最小的额外负担(overhead)。系统核心偶尔会           使用它们。除此之外,interlockedvariables 主要用于引用计数。它们:

  • 允许对 4 字节的数值有些基本的同步操作,不需动用到 criticalsection 或 mutex 之类。
  • 在 SMP(Symmetric Multi-Processors)操作系统中亦可有效运作。