【Windows核心编程学习笔记】用户模式下的线程同步之二---关键段(critical section)
关键段是一小段代码,它在执行之前需要独占对一些共享资源的访问权。这种方式可以让多行代码以“原子方式”(指的是除了当前线程之外,没有其他任何线程会同时访问该资源)来对资源进行操控。当然,系统仍然可以暂停当前线程去调度其他线程。但是,在当前线程离开关键段之前,系统不会调度任何想要访问统一资源的其他线程的。
首先看一段代码:
我们首先定义了一个名为g_cs的CRITICAL_SECTION数据结构然后把需要访问共享资源的代码放在EnterCriticalSection()和LeaveCriticalSection()函数之间。注意,在调用这两个函数的时候传入的是变量g_cs的地址。
需要注意的地方有:当我们有一个资源,或者多个总是在一起使用的资源,那么我们可以创建一个CRITICAL_SECTION结构来保护这些资源。如果有多个不总是在一起使用的资源,那么应该创建多个CRITICAL_SECTION结构。
关键段的优点是:容易使用,它们内部其实是用interlocked函数实现的,因此执行速度很快。但是无法用来在多个进程之间对线程进行同步。
一般情况下,我们会将CRITICAL_SECTION结构作为全局变量来分配,这样进程中的所有线程就能够非常方便的通过变量名来方访问这些结构。但是,CRITICAL_SECTION结构也可以作为局部变量来分配,或者从堆中动态分配,另外将他们作为类的一个私有字段来分配也是很常见的。在使用CRITICAL_SECTION结构的时候,只有两个必要条件:
(1)所有要访问资源的线程必须知道用来保护资源的CRITICAL_SECTION结构的地址。
(2)任何线程在试图访问被保护的资源之前,必须对CRITICAL_SECTION结构的内部成员进行初始化。进行初始化的函数是:
VOID DeleteCriticalSection ( LPCRITICAL_SECTION lpCriticalSection // critical section);关键段和旋转锁结合
当线程试图进入一个关键段,但是这个关键段正被另一个线程占用的时候,函数会立即把调用线程切换到等待状态。这意味着线程必须从用户模式切换到内核模式(大约1000个CPU周期),这个切换的开销很大。
为了提高关键段的性能,Microsoft把旋转锁合并到了关键段中。因此,当调用EnterCriticalSection的时候,它会用一个旋转锁不断地循环,尝试在一段时间内获得对资源的访问。只有当尝试失败的时候,线程才会切换到内核模式并进入等待状态。
为了在使用关键段的时候同时使用旋转锁,必须调用下面的函数来初始化关键段: