临界区:

临界区是一种轻量级机制,在某一时间内只允许一个线程执行某个给定代码段。通常在多线程修改全局数据时会使用临界区。事件、信号量也用于多线程同步,但临界区与它们不同,并不总是执行向内核模式的切换,这一转换成本昂贵。要获得一个未占用临界区,事实上只需要对内存做出很少的修改,其速度非常快。只有在尝试获得已占用临界区时,它才会跳至内核模式。这一轻量级特性的缺点在于临界区只能用于对同一进程内的线程进行同步。

临界区由 WINNT.H 中所定义的 RTL_CRITICAL_SECTION 结构表示。 WINBASE.H 后您会发现:

typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;

操作临界区的API函数有:

(1)初始化临界区InitializeCriticalSection

(2)进入临界区EnterCriticalSection

(3)离开临界区LeaveCriticalSection

(4)删除临界区DeleteCriticalSection

在临界区未被使用的理想情况中,对 EnterCriticalSection 的调用非常快速,因为它只是读取和修改用户模式内存中的内存位置。所阻止的线程以内核模式等待,在该临界区的所有者将其释放之前,不能对这些线程进行调度。如果有多个线程被阻止于一个临界区中,当另一线程释放该临界区时,只有一个线程获得该临界区。

RTL_CRITICAL_SECTION 结构

一个进程的临界区是保存于一个链表中,并且可以对其进行枚举。实际上,WINDBG 支持 !locks 命令,这一命令可以列出目标进程中的所有临界区。

RTL_CRITICAL_SECTION 结构如下:

struct RTL_CRITICAL_SECTION

{

PRTL_CRITICAL_SECTION_DEBUG DebugInfo;

LONG LockCount;

LONG RecursionCount;

HANDLE OwningThread;

HANDLE LockSemaphore;

ULONG_PTR SpinCount;

};

以下各段对每个字段进行说明。

DebugInfo 此字段包含一个指针,指向系统分配的伴随结构,该结构的类型为 RTL_CRITICAL_SECTION_DEBUG。这一结构中包含更多极有价值的信息,也定义于 WINNT.H 中。LockCount 这是临界区中最重要的一个字段。它被初始化为数值 -1;此数值等于或大于 0 时,表示此临界区被占用。当其不等于 -1 时,OwningThread 字段包含了拥有此临界区的线程 ID。此字段与 (RecursionCount-1) 数值之间的差值表示有多少个其他线程在等待获得该临界区。

RecursionCount 此字段包含所有者线程已经获得该临界区的次数。如果该数值为零,下一个尝试获取该临界区的线程将会成功。

OwningThread 此字段包含当前占用此临界区的线程的线程标识符。此线程 ID 与 GetCurrentThreadId 之类的 API 所返回的 ID 相同。

LockSemaphore 它是一个内核对象句柄,用于通知操作系统:该临界区现在空闲。操作系统在一个线程第一次尝试获得该临界区,但被另一个已经拥有该临界区的线程所阻止时,自动创建这样一个句柄。应当调用 DeleteCriticalSection(它将发出一个调用该事件的 CloseHandle 调用,并在必要时释放该调试结构),否则将会发生资源泄漏。

SpinCount 仅用于多处理器系统。MSDN文档对此字段进行如下说明:“在多处理器系统中,如果该临界区不可用,调用线程将在对与该临界区相关的信号执行等待操作之前,旋转 dwSpinCount 次。如果该临界区在旋转操作期间变为可用,该调用线程就避免了等待操作。”旋转计数可以在多处理器计算机上提供更佳性能,其原因在于在一个循环中旋转通常要快于进入内核模式等待状态。此字段默认值为零,但可以用 InitializeCriticalSectionAndSpinCount API 将其设置为一个不同值。

RTL_CRITICAL_SECTION_DEBUG结构如下:

struct _RTL_CRITICAL_SECTION_DEBUG

{

WORD   Type;

WORD   CreatorBackTraceIndex;

RTL_CRITICAL_SECTION *CriticalSection;

LIST_ENTRY ProcessLocksList;

DWORD EntryCount;

DWORD ContentionCount;

DWORD Spare[ 2 ];

}

这一结构由InitializeCriticalSection分配和初始化。它既可以由NTDLL内的预分配数组分配,也可以由进程堆分配。RTL_CRITICAL_SECTION的这一伴随结构包含一组匹配字段,具有迥然不同的角色:有两个难以理解,随后两个提供了理解这一临界区链结构的关键,两个是重复设置的,最后两个未使用。

下面是对 RTL_CRITICAL_SECTION 字段的说明。

Type 此字段未使用,被初始化为数值 0。

CreatorBackTraceIndex 此字段仅用于诊断情形中。在注册表项 HKLM\Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\YourProgram 之下是 keyfield、GlobalFlag 和 StackTraceDatabaseSizeInMb 值。注意,只有在运行稍后说明的 Gflags 命令时才会显示这些值。这些注册表值的设置正确时,CreatorBackTraceIndex 字段将由堆栈跟踪中所用的一个索引值填充。在 MSDN 中搜索 GFlags 文档中的短语“create user mode stack trace database”和“enlarging the user-mode stack trace database”,可以找到有关这一内容的更多信息。

CriticalSection 指向与此结构相关的 RTL_CRITICAL_SECTION。图 1 说明该基础结构以及 RTL_CRITICAL_SECTION、RTL_CRITICAL_SECTION_DEBUG 和事件链中其他参与者之间的关系。

图 1 临界区处理流程

ProcessLocksList LIST_ENTRY 是用于表示双向链表中节点的标准 Windows 数据结构。RTL_CRITICAL_SECTION_DEBUG 包含了链表的一部分,允许向前和向后遍历该临界区。本文后面给出的实用工具说明如何使用 Flink(前向链接)和 Blink(后向链接)字段在链表中的成员之间移动。任何从事过设备驱动程序或者研究过 Windows 内核的人都会非常熟悉这一数据结构。

EntryCount/ContentionCount 这些字段在相同的时间、出于相同的原因被递增。这是那些因为不能马上获得临界区而进入等待状态的线程的数目。与 LockCount 和 RecursionCount 字段不同,这些字段永远都不会递减。

Spares 这两个字段未使用,甚至未被初始化(尽管在删除临界区结构时将这些字段进行了清零)。后面将会说明,可以用这些未被使用的字段来保存有用的诊断值。

总结:

(1)如果 LockCount 字段有一个不等于 -1 的数值,此临界区被占用,OwningThread 字段包含拥有该临界区的线程的线程标识符。

(2)如果 RecursionCount 是一个大于 1 的数值,其告知您所有者线程已经重新获得该临界区多少次(也许不必要)。

(3)LockCount 与 RecursionCount 字段中分别包含其初始值 -1 和 0,这一点非常重要。事实上,对于单线程程序,不能仅通过检查这些字段来判断是否曾获得过临界区。但是,多线程程序留下了一些标记,可以用来判断是否有两个或多个线程试图同时拥有同一临界区。

(4)在该临界区未被占用时 LockSemaphore 字段中仍包含一个非零值。这表示:在某一时间,此临界区阻止了一个或多个线程。事件句柄用于通知该临界区已被释放,等待该临界区的线程之一现在可以获得该临界区并继续执行。因为 OS 在临界区阻止另一个线程时自动分配事件句柄,所以如果您在不再需要临界区时忘记将其删除,LockSemaphore 字段可能会导致程序中发生资源泄漏。

(5)在多线程程序中可能遇到的另一状态是 EntryCount 和 ContentionCount 字段包含一个大于零的数值。这两个字段保存有临界区对一个线程进行阻止的次数。在每次发生这一事件时,这两个字段被递增,但在临界区存在期间不会被递减。这些字段可用于间接确定程序的执行路径和特性。例如,EntryCount 非常高时则意味着该临界区经历着大量争用,可能会成为代码执行过程中的一个潜在瓶颈。

(6)可以通过RTL_CRITICAL_SECTION_DEBUG 中的LIST_ENTRY 遍历进程中的临界区,Flink=NULL为表头,Blink=NULL为表尾。

(7)利用RTL_CRITICAL_SECTION 的 Spare 字段可以区分我们定义的临界区和系统定义的临界区。

但是如何知道哪些线程等待某个临界区呢?

windows临界区的更多相关文章

  1. 十二 windows临界区,其他各种mutex

    一.windows临界区 类似于互斥量 == 临界区. 二.多次进入临界区 进入临界区(加锁): 离开临界区(解锁): 同一个线程中windows中相同临界区变量代表的临界区进入(entercirti ...

  2. windows 线程同步

    Windows 临界区,内核事件,互斥量,信号量. 临界区,内核事件,互斥量,信号量,都能完成线程的同步,在这里把他们各自的函数调用,结构定义,以及适用情况做一个总结. 临界区: 适用范围:它只能同步 ...

  3. 学习实践:使用模式,原则实现一个C++数据库訪问类

    一.概述 在我參与的多个项目中.大家使用libMySQL操作MySQL数据库,并且是源代码级复用,在多个项目中同样或相似的源代码.这种复用方式给开发带来了不便. libMySQL的使用比較麻烦.非常e ...

  4. C ++ _多线程笔记

    #include<iostream>#include <thread>//创建线程需要添加的头文件 using namespace std;/*thread join(阻塞:主 ...

  5. C++多线程基础学习笔记(十)

    一.Windows临界区的基本用法 CRITICAL_SECTION my_winsc;              //定义一个Windows的临界区,相当于一个mutex变量 InitializeC ...

  6. 学习实践:使用模式,原则实现一个C++数据库访问类

    一.概述 在我参与的多个项目中,大家使用libMySQL操作MySQL数据库,而且是源码即复用,在多个项目中有多套相同或相似的源码,这样的复用方式给开发带来了不变,而且libMySQL的使用比较麻烦, ...

  7. windows多线程同步--临界区

    推荐参考博客:秒杀多线程第五篇 经典线程同步 关键段CS   关于临界区的观念,一般操作系统书上面都有. 适用范围:它只能同步一个进程中的线程,不能跨进程同步.一般用它来做单个进程内的代码快同步,效率 ...

  8. 线程同步(windows平台):临界区

    一:介绍 临界区指的是一个访问共用资源(例:全局变量)的程序片段,该共用资源无法同时被多个线程访问的特性.有多个线程试图同时访问临界区,那么在有一个线程进入后其他所有试图访问此临界区的线程将被挂起,并 ...

  9. windows系统调用 临界区机制

    #include "iostream" #include "windows.h" #include "cstring" using name ...

随机推荐

  1. RDLC报表 在WinForm里运行出现 未能加载文件或程序集microsoft.reportviewer.winforms

    做一下记录顺便帮助一下遇到问题的朋友. 做RDLC报表在WinForm运行,在VS里面调试引用了“Microsoft.ReportViewer.Common.dll”和“Microsoft.Repor ...

  2. 关于IT概念的一些思考

    同事提及“软件工程.软件生命周期.项目管理.CMMI.IPD.RUP.UML及UML建模.面向对象分析与设计.需求分析.系统分析与设计……等等,它们到底是什么?它们之间有什么关系?”   下面是个人见 ...

  3. SSAS:菜鸟摸门

    官方:SSAS 多维模型 Analysis Services 多维解决方案使用多维数据集结构来分析多个维度之间的业务数据. 多维模式是 Analysis Services 的默认服务器模式. 它包括针 ...

  4. 如何让DevExpress TreeList的每个结点高亮显示?

    概述:如何让DevExpress TreeList的每个节点高亮显示? 如何让DXperience TreeList的每个节点高亮显示? 效果如下: private void treeList1_Cu ...

  5. 【Android】与服务器实现JSON数据通信

    一.前言 作为一名移动端开发人员,具备一定的服务端开发能力也是非常必要的,本篇博客讲述如何在Android和服务器之间实现JSON数据通信交互,博客内容基于另外一篇博客:[Web]Eclipse + ...

  6. 类或方法名后加<>

    声明类型为泛型. 说明传递的参数为泛型,而不仅仅是一种类型. public void aa<T>(T a) { Console.WriteLine(a); } 调用时可写: this.aa ...

  7. java攻城狮之路(Android篇)--ListView与ContentProvider

    一.ListView 1.三种Adapter构建ListView ListView添加条目的时候, 可以使用setAdapter(ListAdapter)方法, 常用的ListAdapter有三种 B ...

  8. Inkpad绘图原理浅析

    本文新版本已转移到开源中国,欢迎前往指导. Inkpad是一款非常优秀的iPad矢量绘图软件,保管你一看见就忘不了.我的感觉是“一览众山小”.“相见甚晚”,以至于我写的TouchVG就是“小巫见大巫” ...

  9. ruby 除法运算

    在Ruby中根据运算对象的值的不同进行不同的操作.除法运算符"/"的两边同为Interger对象时运算符进行整除运算,其中任意一方为Float对象时进行实数的除法运算. 7 / 2 ...

  10. 企业云部署要如何选择IaaS PaaS和SaaS

    1为什么IaaS成了灵丹妙药   我非常惊讶,为什么很多传统企业已经接受了云计算,但接受的方式却往往不尽人意.对大多数企业来说,云计算的投入产出比相对较小,并且局限于基础设施层的环节. 就目前而言,大 ...