critical_critical value怎么算 - CSDN
精华内容
参与话题
  • VC++ 线程同步 CRITICAL_SECTION

    千次阅读 2012-05-10 20:46:24
    声明:本人一个菜鸟,网上搜罗了很多关于VC++编程的资料,可每本资料都是涵盖面太广,难以细致。英语又太烂,所以不得意只得摸索,恐又忘记所以记在此处,若有不对的地方,烦劳指出,不胜...CRITICAL_SECTION g_csLock;

    声明:本人一个菜鸟,网上搜罗了很多关于VC++编程的资料,可每本资料都是涵盖面太广,难以细致。英语又太烂,所以不得意只得摸索,恐又忘记所以记在此处,若有不对的地方,烦劳指出,不胜感激。

    author:fym0121@163.com

    license:GPL

    IDE:Visual Studio 2008


    临界区,主要函数

    CRITICAL_SECTION g_csLock;           //定义
    InitializeCriticalSection(&g_csLock);   // 初始化
    EnterCriticalSection(&g_csLock);       //进入
    LeaveCriticalSection(&g_csLock);      //离开  
    DeleteCriticalSection(&g_csLock);     //销毁
    

    实例

    #include "stdafx.h"
    #include <Windows.h>
    #include <process.h>  //_beginthreadex
    #include <iostream>
    
    int g_candy = 0;
    CRITICAL_SECTION g_csLock;
    unsigned int _stdcall thread_bob(void *);  //_beginthreadex P3
    
    int _tmain(int argc, _TCHAR* argv[])
    {
        // this is Alice
        InitializeCriticalSection(&g_csLock);
        unsigned threadID;
        //创建线程
        _beginthreadex(NULL,0,thread_bob,NULL,0,&threadID);
    
        while(1)
        {
            EnterCriticalSection(&g_csLock);  // 进入临界区
            /* 注意,加上下面这句话,不会对Alice线程产生影响,但是Bob线程走不动了 */
            //EnterCriticalSection(&g_csLock);
            std::cerr <<"Alice: "<<g_candy++ <<std::endl;
            Sleep(3000);
    
            /* 如果上面两个 EnterCriticalSection,下面两个LeaveCriticalSection,则两个线程都可以跑起来*/
            LeaveCriticalSection(&g_csLock);  //离开临街区
            //LeaveCriticalSection(&g_csLock);
        }
    	return 0;
    }
    
    unsigned int _stdcall thread_bob(void*)
    {
        // this is Bob
        while (1)
        {
            EnterCriticalSection(&g_csLock);
            std::cerr <<"Bob: " << g_candy++ <<std::endl;
            LeaveCriticalSection(&g_csLock);
            Sleep(1000);
        }
    }

    貌似,在一个线程内,同一个临界区可以重复进入,而不会对这个线程产生什么影响,但另一个线程就惨了,它根本得不到锁。Alice线程进两次临界区,出一次,对Bob来说相当于Alice还在临界区里。

    展开全文
  • 深入理解CRITICAL_SECTION

    千次阅读 2014-08-27 22:14:36
    深入理解CRITICAL_SECTION 摘要 临界区是一种防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注,因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时,对  Windows  ...
    
    

    摘要


    临界区是一种防止多个线程同时执行一个特定代码节的机制,这一主题并没有引起太多关注,因而人们未能对其深刻理解。在需要跟踪代码中的多线程处理的性能时,对 
    Windows 
    中临界区的深刻理解非常有用。本文深入研究临界区的原理,以揭示在查找死锁和确认性能问题过程中的有用信息。它还包含一个便利的实用工具程序,可以显示所有临界区及其当前状态。


    在我们许多年的编程实践中,对于 Win32 临界区没有受到非常多的“under the 
    hood”关注而感到非常奇怪。当然,您可能了解有关临界区初始化与使用的基础知识,但您是否曾经花费时间来深入研究 WINNT.H 中所定义的 
    CRITICAL_SECTION 
    结构呢?在这一结构中有一些非常有意义的好东西被长期忽略。我们将对此进行补充,并向您介绍一些很有意义的技巧,这些技巧对于跟踪那些难以察觉的多线程处理错误非常有用。更重要的是,使用我们的 
    MyCriticalSections 实用工具,可以明白如何对 CRITICAL_SECTION 
    进行微小地扩展,以提供非常有用的特性,这些特性可用于调试和性能调整(要下载完整代码,参见本文顶部的链接)。


    老实说,作者们经常忽略 CRITICAL_SECTION 结构的部分原因在于它在以下两个主要 Win32 代码库中的实现有很大不同:Microsoft 
    Windows 95 和 Windows NTH嗣侵勒饬街执肟舛家丫⒄钩龃罅亢笮姹荆ㄆ渥钚掳姹痉直鹞 Windows Me 和 Windows 
    XP),但没有必要在此处将其一一列出。关键在于 Windows XP 现在已经发展得非常完善,开发商可能很快就会停止对 Windows 95 
    系列操作系统的支持。我们在本文中就是这么做的。


    诚然,当今最受关注的是 Microsoft .NET Framework,但是良好的旧式 Win32 编程不会很快消失。如果您拥有采用了临界区的现有 
    Win32 代码,您会发现我们的工具以及对临界区的说明都非常有用。但是请注意,我们只讨论 Windows NT 及其后续版本,而没有涉及与 .NET 
    相关的任何内容,这一点非常重要。


    临界区:简述


    如果您非常熟悉临界区,并可以不假思索地进行应用,那就可以略过本节。否则,请向下阅读,以对这些内容进行快速回顾。如果您不熟悉这些基础内容,则本节之后的内容就没有太大意义。


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


    临界区由 WINNT.H 中所定义的 RTL_CRITICAL_SECTION 结构表示。因为您的 C++ 代码通常声明一个 
    CRITICAL_SECTION 类型的变量,所以您可能对此并不了解。研究 WINBASE.H 后您会发现:

    typedef RTL_CRITICAL_SECTION CRITICAL_SECTION;
    

    我们将在短时间内揭示 RTL_CRITICAL_SECTION 结构的实质。此时,重要问题在于 CRITICAL_SECTION(也称作 
    RTL_CRITICAL_SECTION)只是一个拥有易访问字段的结构,这些字段可以由 KERNEL32 API 操作。


    在将临界区传递给 InitializeCriticalSection 
    时(或者更准确地说,是在传递其地址时),临界区即开始存在。初始化之后,代码即将临界区传递给 EnterCriticalSection 和 
    LeaveCriticalSection API。一个线程自 EnterCriticalSection 中返回后,所有其他调用 
    EnterCriticalSection 的线程都将被阻止,直到第一个线程调用 LeaveCriticalSection 
    为止。最后,当不再需要该临界区时,一种良好的编码习惯是将其传递给 DeleteCriticalSection。


    在临界区未被使用的理想情况中,对 EnterCriticalSection 
    的调用非常快速,因为它只是读取和修改用户模式内存中的内存位置。否则(在后文将会遇到一种例外情况),阻止于临界区的线程有效地完成这一工作,而不需要消耗额外的 
    CPU 
    周期。所阻止的线程以内核模式等待,在该临界区的所有者将其释放之前,不能对这些线程进行调度。如果有多个线程被阻止于一个临界区中,当另一线程释放该临界区时,只有一个线程获得该临界区。


    深入研究: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 字段(此字段被错误地定义于 WINNT.H 中 — 应当是 DWORD 而不是 
    HANDLE)包含了拥有此临界区的线程 ID。此字段与 (RecursionCount -1) 数值之间的差值表示有多少个其他线程在等待获得该临界区。


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


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


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


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


    RTL_CRITICAL_SECTION_DEBUG 结构


    前面我们注意到,在 RTL_CRITICAL_SECTION 结构内,DebugInfo 字段指向一个 
    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 
    和事件链中其他参与者之间的关系。


    深入理解CRITICAL_SECTION - 飞秋,www.freeeim.com - 飞秋官网

    图 1 临界区处理流程



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


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


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


    即使 RTL_CRITICAL_SECTION_DEBUG 
    中包含多个字段,它也是常规临界区结构的必要成分。事实上,如果系统恰巧不能由进程堆中获得这一结构的存储区,InitializeCriticalSection 
    将返回为 STATUS_NO_MEMORY 的 LastError 结果,然后返回处于不完整状态的临界区结构。


    临界区状态


    当程序执行、进入与离开临界区时,RTL_CRITICAL_SECTION 和 RTL_CRITICAL_SECTION_DEBUG 
    结构中的字段会根据临界区所处的状态变化。这些字段由临界区 API 
    中的簿记代码更新,在后面将会看到这一点。如果程序为多线程,并且其线程访问是由临界区保护的公用资源,则这些状态就更有意义。


    但是,不管代码的线程使用情况如何,有两种状态都会出现。第一种情况,如果 LockCount 字段有一个不等于 -1 
    的数值,此临界区被占用,OwningThread 字段包含拥有该临界区的线程的线程标识符。在多线程程序中,LockCount 与 RecursionCount 
    联合表明当前有多少线程被阻止于该临界区。第二种情况,如果 RecursionCount 是一个大于 1 
    的数值,其告知您所有者线程已经重新获得该临界区多少次(也许不必要),该临界区既可以通过调用 EnterCriticalSection、也可以通过调用 
    TryEnterCriticalSection 获得。大于 1 的任何数值都表示代码的效率可能较低或者可能在以后发生错误。例如,访问公共资源的任何 C++ 
    类方法可能会不必要地重新进入该临界区。


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


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


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


    在研究一个死锁程序时,还会发现一种似乎无法进行逻辑解释的状态。一个使用非常频繁的临界区的 LockCount 字段中包含一个大于 -1 
    的数值,也就是说它被线程所拥有,但是 OwningThread 
    字段为零(这样就无法找出是哪个线程导致问题)。测试程序是多线程的,在单处理器计算机和多处理器计算机中都会出现这种情况。尽管 LockCount 
    和其他值在每次运行中都不同,但此程序总是死锁于同一临界区。我们非常希望知道是否有任何其他开发人员也遇到了导致这一状态的 API 调用序列。


    构建一个更好的捕鼠器


    在我们学习临界区的工作方式时,非常偶然地得到一些重要发现,利用这些发现可以得到一个非常好的实用工具。第一个发现是 ProcessLocksList 
    LIST_ENTRY 
    字段的出现,这使我们想到进程的临界区可能是可枚举的。另一个重大发现是我们知道了如何找出临界区列表的头。还有一个重要发现是可以在没有任何损失的情况下写 
    RTL_CRITICAL_SECTION 的 Spare 
    字段(至少在我们的所有测试中如此)。我们还发现可以很容易地重写系统的一些临界区例程,而不需要对源文件进行任何修改。


    最初,我们由一个简单的程序开始,其检查一个进程中的所有临界区,并列出其当前状态,以查看是否拥有这些临界区。如果拥有,则找出由哪个线程拥有,以及该临界区阻止了多少个线程?这种做法对于 
    OS 的狂热者们比较适合,但对于只是希望有助于理解其程序的典型的程序员就不是非常有用了。


    即使是在最简单的控制台模式“Hello World”程序中也存在许多临界区。其中大部分是由 USER32 或 GDI32 之类的系统 DLL 创建,而这些 
    DLL 很少会导致死锁或性能问题。我们希望有一种方法能滤除这些临界区,而只留下代码中所关心的那些临界区。RTL_CRITICAL_SECTION_DEBUG 
    结构中的 Spare 字段可以很好地完成这一工作。可以使用其中的一个或两个来指示:这些临界区是来自用户编写的代码,而不是来自 OS。


    于是,下一个逻辑问题就变为如何确定哪些临界区是来自您编写的代码。有些读者可能还记得 Matt Pietrek 2001 年 1 月的 Under The Hood 专栏中的 LIBCTINY.LIB。LIBCTINY 所采用的一个技巧是一个 LIB 
    文件,它重写了关键 Visual C++ 运行时例程的标准实现。将 LIBCTINY.LIB 文件置于链接器行的其他 LIB 
    之前,链接器将使用这一实现,而不是使用 Microsoft 所提供的导入库中的同名后续版本。


    为对临界区应用类似技巧,我们创建 InitializeCriticalSection 的一个替代版本及其相关导入库。将此 LIB 文件置于 
    KERNEL32.LIB 之前,链接器将链接我们的版本,而不是 KERNEL32 中的版本。对 InitializeCriticalSection 
    的实现显示在图 2 中。此代码在概念上非常简单。它首先调用 KERNEL32.DLL 中的实际 
    InitializeCriticalSection。接下来,它获得调用 InitializeCriticalSection 的代码地址,并将其贴至 
    RTL_CRITICAL_SECTION_DEBUG 结构的备用字段之一。我们的代码如何确定调用代码的地址呢?x86 CALL 
    指令将返回地址置于堆栈中。CriticalSectionHelper 代码知道该返回地址位于堆栈帧中一个已知的固定位置。


    实际结果是:与 CriticalSectionHelper.lib 正确链接的任何 EXE 或 DLL 都将导入我们的 DLL 
    (CriticalSectionHelper.DLL),并占用应用了备用字段的临界区。这样就使事情简单了许多。现在我们的实用工具可以简单地遍历进程中的所有临界区,并且只显示具有正确填充的备用字段的临界区信息。那么需要为这一实用工具付出什么代价呢?请稍等,还有更多的内容!


    因为您的所有临界区现在都包含对其进行初始化时的地址,实用工具可以通过提供其初始化地址来识别各个临界区。原始代码地址本身没有那么有用。幸运的是,DBGHELP.DLL 
    使代码地址向源文件、行号和函数名称的转换变得非常容易。即使一个临界区中没有您在其中的签名,也可以将其地址提交给 
    DBGHELP.DLL。如果将其声明为一个全局变量,并且如果符号可用,则您就可以在原始源代码中确定临界区的名称。顺便说明一下,如果通过设置 
    _NT_SYMBOL_PATH 环境变量,并设置 DbgHelp 以使用其 Symbol Server 下载功能,从而使 DbgHelp 
    发挥其效用,则会得到非常好的结果。


    MyCriticalSections 实用工具


    我们将所有这些思想结合起来,提出了 MyCriticalSections 程序。MyCriticalSections 
    是一个命令行程序,在不使用参数运行该程序时可以看到一些选项:

    Syntax: MyCriticalSections <PID> [options]
    Options:
    /a = all critical sections
    /e = show only entered critical sections
    /v = verbose
    

    唯一需要的参数是 Program ID 或 PID(十进制形式)。可以用多种方法获得 PID,但最简单的方法可能就是通过 Task 
    Manager。在没有其他选项时,MyCriticalSections 列出了来自代码模块的所有临界区状态,您已经将 
    CriticalSectionHelper.DLL 
    链接至这些代码模块。如果有可用于这一(些)模块的符号,代码将尝试提供该临界区的名称,以及对其进行初始化的位置。


    要查看 MyCriticalSections 是如何起作用的,请运行 Demo.EXE 程序,该程序包含在下载文件中。Demo.EXE 
    只是初始化两个临界区,并由一对线程进入这两个临界区。图 3 显示运行“MyCriticalSections 2040”的结果(其中 2040 为 Demo.EXE 的 
    PID)。


    在该图中,列出了两个临界区。在本例中,它们被命名为 csMain 和 yetAnotherCriticalSection。每个“Address:”行显示了 
    CRITICAL_SECTION 的地址及其名称。“Initialized in”行包含了在其中初始化 CRITICAL_SECTION 
    的函数名。代码的“Initialized at”行显示了源文件和初始化函数中的行号。


    对于 csMain 临界区,您将看到锁定数为 0、递归数为 
    1,表示一个已经被一线程获得的临界区,并且没有其他线程在等待该临界区。因为从来没有线程被阻止于该临界区,所以 Entry Count 字段为 0。


    现在来看 yetAnotherCriticalSection,会发现其递归数为 3。快速浏览 Demo 代码可以看出:主线程调用 
    EnterCriticalSection 三次,所以事情的发生与预期一致。但是,还有一个第二线程试图获得该临界区,并且已经被阻止。同样,LockCount 
    字段也为 3。此输出显示有一个等待线程。


    MyCriticalSections 拥有一些选项,使其对于更为勇敢的探索者非常有用。/v 
    开关显示每个临界区的更多信息。旋转数与锁定信号字段尤为重要。您经常会看到 NTDLL 和其他 DLL 
    拥有一些旋转数非零的临界区。如果一个线程在获得临界区的过程中曾被锁定,则锁定信号字段为非零值。/v 开关还显示了 
    RTL_CRITICAL_SECTION_DEBUG 结构中备用字段的内容。


    /a 开关显示进程中的所有临界区,即使其中没有 CriticalSectionHelper.DLL 签名也会显示。如果使用 
    /a,则请做好有大量输出的准备。真正的黑客希望同时使用 /a 和 /v,以显示进程中全部内容的最多细节。使用 /a 的一个小小的好处是会看到 NTDLL 
    中的LdrpLoaderLock 临界区。此临界区在 DllMain 调用和其他一些重要时间内被占用。LdrpLoaderLock 
    是许多不太明显、表面上难以解释的死锁的形成原因之一。(为使 MyCriticalSection 能够正确标记 LdrpLoaderLock 实例,需要用于 
    NTDLL 的 PDB 文件可供使用。)


    /e 开关使程序仅显示当前被占用的临界区。未使用 /a 开关时,只显示代码中被占用的临界区(如备用字段中的签名所指示)。采用 /a 
    开关时,将显示进程中的全部被占用临界区,而不考虑其来源。


    那么,希望什么时候运行 MyCriticalSections 
    呢?一个很明确的时间是在程序被死锁时。检查被占用的临界区,以查看是否有什么使您惊讶的事情。即使被死锁的程序正运行于调试器的控制之下,也可以使用 
    MyCriticalSections。


    另一种使用 MyCriticalSections 的时机是在对有大量多线程的程序进行性能调整时。在阻塞于调试器中的一个使用频繁、非重入函数时,运行 
    MyCriticalSections,查看在该时刻占用了哪些临界区。如果有很多线程都执行相同任务,就非常容易导致一种情形:一个线程的大部分时间被消耗在等待获得一个使用频繁的临界区上。如果有多个使用频繁的临界区,这造成的后果就像花园的浇水软管打了结一样。解决一个争用问题只是将问题转移到下一个容易造成阻塞的临界区。


    一个查看哪些临界区最容易导致争用的好方法是在接近程序结尾处设置一个断点。在遇到断点时,运行 MyCriticalSections 并查找具有最大 
    Entry Count 值的临界区。正是这些临界区导致了大多数阻塞和线程转换。


    尽管 MyCriticalSections 运行于 Windows 2000 及更新版本,但您仍需要一个比较新的 DbgHelp.DLL 版本 - 5.1 
    版或更新版本。Windows XP 中提供这一版本。也可以由其他使用 DbgHelp 的工具中获得该版本。例如,Debugging Tools For Windows 下载中通常拥有最新的 DbgHelp.DLL。


    深入研究重要的临界区例程


    此最后一节是为那些希望理解临界区实现内幕的勇敢读者提供的。对 NTDLL 进行仔细研究后可以为这些例程及其支持子例程创建伪码(见下载中的 
    NTDLL(CriticalSections).cpp)。以下 KERNEL32 API 组成临界区的公共接口:

    InitializeCriticalSection
    InitializeCriticalSectionAndSpinCount
    DeleteCriticalSection
    TryEnterCriticalSection
    EnterCriticalSection
    LeaveCriticalSection
    

    前两个 API 只是分别围绕 NTDLL API RtlInitializeCriticalSection 和 
    RtlInitializeCriticalSectionAndSpinCount 的瘦包装。所有剩余例程都被提交给 NTDLL 中的函数。另外,对 
    RtlInitializeCriticalSection 的调用是另一个围绕 RtlInitializeCriticalSectionAndSpinCount 
    调用的瘦包装,其旋转数的值为 0。使用临界区的时候实际上是在幕后使用以下 NTDLL API:

    RtlInitializeCriticalSectionAndSpinCount
    RtlEnterCriticalSection
    RtlTryEnterCriticalSection
    RtlLeaveCriticalSection
    RtlDeleteCriticalSection
    

    在这一讨论中,我们采用 Kernel32 名称,因为大多数 Win32 程序员对它们更为熟悉。


    InitializeCriticalSectionAndSpinCount 对临界区的初始化非常简单。RTL_CRITICAL_SECTION 
    结构中的字段被赋予其起始值。与此类似,分配 RTL_CRITICAL_SECTION_DEBUG 结构并对其进行初始化,将 
    RtlLogStackBackTraces 调用中的返回值赋予 CreatorBackTraceIndex,并建立到前面临界区的链接。


    顺便说一声,CreatorBackTraceIndex 一般接收到的值为 0。但是,如果有 Gflags 和 Umdh 
    实用工具,可以输入以下命令:

    Gflags /i MyProgram.exe +ust
    Gflags /i MyProgram.exe /tracedb 24
    

    这些命令使得 MyProgram 的“Image File Execution Options”下添加了注册表项。在下一次执行 MyProgram 
    时会看到此字段接收到一个非 0 数值。有关更多信息,参阅知识库文章 Q268343“Umdhtools.exe:How to Use Umdh.exe to Find Memory 
    Leaks
    ”。临界区初始化中另一个需要注意的问题是:前 64 个 RTL_CRITICAL_SECTION_DEBUG 
    结构不是由进程堆中分配,而是来自位于 NTDLL 内的 .data 节的一个数组。


    在完成临界区的使用之后,对 DeleteCriticalSection(其命名不当,因为它只删除 RTL_CRITICAL_SECTION_ 
    DEBUG)的调用遍历一个同样可理解的路径。如果由于线程在尝试获得临界区时被阻止而创建了一个事件,将通过调用 ZwClose 来销毁该事件。接下来,在通过 
    RtlCriticalSectionLock 获得保护之后(NTDLL 以一个临界区保护它自己的内部临界区列表 — 
    您猜对了),将调试信息从链中清除,对该临界区链表进行更新,以反映对该信息的清除操作。该内存由空值填充,并且如果其存储区是由进程堆中获得,则调用 
    RtlFreeHeap 将使得其内存被释放。最后,以零填充 RTL_CRITICAL_SECTION。


    有两个 API 要获得受临界区保护的资源 — TryEnterCriticalSection 和 
    EnterCriticalSection。如果一个线程需要进入一个临界区,但在等待被阻止资源变为可用的同时,可执行有用的工作,那么 
    TryEnterCriticalSection 正是您需要的 API。此例程测试此临界区是否可用;如果该临界区被占用,该代码将返回值 
    FALSE,为该线程提供继续执行另一任务的机会。否则,其作用只是相当于 EnterCriticalSection。


    如果该线程在继续进行之前确实需要拥有该资源,则使用 EnterCriticalSection。此时,取消用于多处理器计算机的 SpinCount 
    测试。这一例程与 TryEnterCriticalSection 类似,无论该临界区是空闲的或已经被该线程所拥有,都调整对该临界区的簿记。注意,最重要的 
    LockCount 递增是由 x86“lock”前缀完成的,这一点非常重要。这确保了在某一时间内只有一个 CPU 可以修改该 LockCount 
    字段。(事实上,Win32 InterlockedIncrement API 只是一个具有相同锁定前缀的 ADD 指令。)


    如果调用线程无法立即获得该临界区,则调用 RtlpWaitForCriticalSection 
    将该线程置于等待状态。在多处理器系统中,EnterCriticalSection 旋转 SpinCount 
    所指定的次数,并在每次循环访问中测试该临界区的可用性。如果此临界区在循环期间变为空闲,该线程获得该临界区,并继续执行。


    RtlpWaitForCriticalSection 
    可能是这里所给的所有过程中最为复杂、最为重要的一个。这并不值得大惊小怪,因为如果存在一个死锁并涉及临界区,则利用调试器进入该进程就可能显示出 
    RtlpWaitForCriticalSection 内 ZwWaitForSingleObject 调用中的至少一个线程。


    如伪码中所显示,在 RtlpWaitForCriticalSection 中有一点簿记工作,如递增 EntryCount 和 
    ContentionCount 字段。但更重要的是:发出对 LockSemaphore 的等待,以及对等待结果的处理。默认情况是将一个空指针作为第三个参数传递给 
    ZwWaitForSingleObject 
    调用,请求该等待永远不要超时。如果允许超时,将生成调试消息字符串,并再次开始等待。如果不能从等待中成功返回,就会产生中止该进程的错误。最后,在从 
    ZwWaitForSingleObject 调用中成功返回时,则执行从 RtlpWaitForCriticalSection 
    返回,该线程现在拥有该临界区。


    RtlpWaitForCriticalSection 必须认识到的一个临界条件是该进程正在被关闭,并且正在等待加载程序锁定 
    (LdrpLoaderLock) 临界区。RtlpWaitForCriticalSection 一定不能 
    允许该线程被阻止,但是必须跳过该等待,并允许继续进行关闭操作。


    LeaveCriticalSection 不像 EnterCriticalSection 那样复杂。如果在递减 RecursionCount 
    之后,结果不为 0(意味着该线程仍然拥有该临界区),则该例程将以 ERROR_SUCCESS 状态返回。这就是为什么需要用适当数目的 Leave 调用来平衡 
    Enter 调用。如果该计数为 0,则 OwningThread 字段被清零,LockCount 被递减。如果还有其他线程在等待,例如 LockCount 
    大于或等于 0,则调用 RtlpUnWaitCriticalSection。此帮助器例程创建 
    LockSemaphore(如果其尚未存在),并发出该信号提醒操作系统:该线程已经释放该临界区。作为通知的一部分,等待线程之一退出等待状态,为运行做好准备。


    最后要说明的一点是,MyCriticalSections 程序如何确定临界区链的起始呢?如果有权访问 NTDLL 
    的正确调试符号,则对该列表的查找和遍历非常简单。首先,定位符号 RtlCriticalSectionList,清空其内容(它指向第一个 
    RTL_CRITICAL_SECTION_DEBUG 结构),并开始遍历。但是,并不是所有的系统都有调试符号,RtlCriticalSectionList 
    变量的地址会随 Windows 
    的各个版本而发生变化。为了提供一种对所有版本都能正常工作的解决方案,我们设计了以下试探性方案。观察启动一个进程时所采取的步骤,会看到是以以下顺序对 NTDLL 
    中的临界区进行初始化的(这些名称取自 NTDLL 的调试符号):

    RtlCriticalSectionLock
    DeferedCriticalSection (this is the actual spelling!)
    LoaderLock
    FastPebLock
    RtlpCalloutEntryLock
    PMCritSect
    UMLogCritSect
    RtlpProcessHeapsListLock
    

    因为检查进程环境块 (PEB) 中偏移量 0xA0 
    处的地址就可以找到加载程序锁,所以对该链起始位置的定位就变得比较简单。我们读取有关加载程序锁的调试信息,然后沿着链向后遍历两个链接,使我们定位于 
    RtlCriticalSectionLock 项,在该点得到该链的第一个临界区。有关其方法的说明,请参见图 4


    深入理解CRITICAL_SECTION - 飞秋,www.freeeim.com - 飞秋官网

    图 4 初始化顺序



    小结


    几乎所有的多线程程序均使用临界区。您迟早都会遇到一个使代码死锁的临界区,并且会难以确定是如何进入当前状态的。如果能够更深入地了解临界区的工作原理,则这一情形的出现就不会像首次出现时那样的令人沮丧。您可以研究一个看来非常含糊的临界区,并确定是谁拥有它,以及其他有用细节。如果您愿意将我们的库加入您的链接器行,则可以容易地获得有关您程序临界区使用的大量信息。通过利用临界区结构中的一些未用字段,我们的代码可以仅隔离并命名您的模块所用的临界区,并告知其准确状态。


    有魄力的读者可以很容易地对我们的代码进行扩展,以完成更为异乎寻常的工作。例如,采用与 InitializeCriticalSection 
    挂钩相类似的方式截获 EnterCriticalSection 和 
    LeaveCriticalSection,可以存储最后一次成功获得和释放该临界区的位置。与此类似,CritSect DLL 拥有一个易于调用的 
    API,用于枚举您自己的代码中的临界区。利用 .NET Framework 中的 Windows 窗体,可以相对容易地创建一个 GUI 版本的 
    MyCriticalSections。对我们代码进行扩展的可能性非常大,我们非常乐意看到其他人员所发现和创造的创新性办法。


    有关文章,请参阅:
    Global Flag Reference:Create kernel mode stack trace 
    database

    GFlags Examples:Enlarging the User-Mode Stack Trace 
    Database

    Under the Hood:Reduce EXE and DLL Size with LIBCTINY.LIB


    Matt Pietrek 是一位软件架构师和作者。他就职于 Compuware/NuMega 实验室,身份为 
    BoundsChecker 和“分布式分析器”产品的首席架构师。他已经创作了三本有关 Windows 系统编程的书籍,并是 MSDN 
    Magazine
     的特约编辑。他的 Web 站点 (http://www.wheaty.net) 有关于以前文章和专栏的 FAQ 和信息。


    Jay Hilyard 是 Compuware/NuMega 实验室的 BoundsChecker 
    小组的软件工程师。他、他的妻子和他们的猫是新罕布什尔州的新居民。他的联系方式为 RussOsterlund@adelphia.net 或 Web 站点 http://www.smidgeonsoft.com

    展开全文
  • 目的:CRITICAL指令指定一块同一时间只能被一条线程执行的代码区域注意:如果一条线程正在一个CRITICAL区域执行而另一个线程到达这个区域,并企图执行,那么它将会被阻塞,直到第一个线程离开这个区域.命名是可选项,...

    格式:

    Fortran

     

    !$OMP CRITICAL [ name ]

     

       block

     

    !$OMP END CRITICAL

    C/C++

     

    #pragma omp critical [ name ]  newline

     

       structured_block



    目的:

    • CRITICAL指令指定一块同一时间只能被一条线程执行的代码区域

    注意:

    • 如果一条线程正在一个CRITICAL区域执行而另一个线程到达这个区域,并企图执行,那么它将会被阻塞,直到第一个线程离开这个区域.
    • 命名是可选项,使不同的CRITICAL区域共存:
    • 命名是全局标志符.具有相同命名的不同的CRITICAL区域被当作同一个区域
    • 所有未命名CRITICAL区域被当作同一个区域

    限制:

    • 扩充CRITICAL 块是非法的.

    例子:

    /*在临界区寻找正整数数组的最大元素*/

    int main()
    {
        int max=0;   
        int a[10]={11,2,33,49,113,20,321,250,689,16};  
        #pragma omp parallel for  
        for(int j=0;j<10;j++)  
        {  
            int temp=a[j];
            printf("temp=a[%d]=%d,id=%d\n",j,a[j],omp_get_thread_num()); 
            #pragma omp critical  
            {  
                if(temp>max)  
                    max=temp;  
                printf("temp=%d,max=%d,id=%d\n",temp,max,omp_get_thread_num());
            }  
        }  
        printf("max=%d\n",max);
        return 0;
    }

    gcc -fopenmp -std=c99 -o critical critical.c (-fopenmp编译并行结构、-std=c99是因为for内定义了变量初始类型int j=0)

    ./critical

    输出:

    temp=a[1]=2,id=1
    temp=2,max=2,id=1
    temp=a[3]=49,id=3
    temp=49,max=49,id=3
    temp=a[6]=321,id=6
    temp=321,max=321,id=6
    temp=a[5]=20,id=5
    temp=20,max=321,id=5
    temp=a[9]=16,id=9
    temp=16,max=321,id=9
    temp=a[2]=33,id=2
    temp=33,max=321,id=2
    temp=a[4]=113,id=4
    temp=113,max=321,id=4
    temp=a[8]=689,id=8
    temp=689,max=689,id=8
    temp=a[7]=250,id=7
    temp=250,max=689,id=7
    temp=a[0]=11,id=0
    temp=11,max=689,id=0

    max=689

    或者

    temp=a[0]=11,id=0
    temp=a[4]=113,id=4
    temp=a[9]=16,id=9
    temp=a[5]=20,id=5
    temp=a[6]=321,id=6
    temp=a[3]=49,id=3
    temp=a[1]=2,id=1
    temp=a[8]=689,id=8
    temp=a[7]=250,id=7
    temp=a[2]=33,id=2
    temp=11,max=11,id=0
    temp=250,max=250,id=7
    temp=2,max=250,id=1
    temp=113,max=250,id=4
    temp=49,max=250,id=3
    temp=20,max=250,id=5
    temp=16,max=250,id=9
    temp=689,max=689,id=8
    temp=321,max=689,id=6
    temp=33,max=689,id=2

    max=689

    输出结果表明,第一个printf处是并行运行的,第二个printf位于critical临界区内,critical指定某一区域的代码,每次只能同时被一个线程执行。


    #pragma omp critical [(name)]

    {

       <临界区代码>

    }

    critical的语义是指在任意时刻只有一个(或是最多一个)线程在执行临界区内的代码, 其目的是对于临界区内的线程之间共享的资源进行保护,比如几个线程对一个公共链表进行插入或删除操作,此时就需要使用critical, 否者该链表中的数据可能不一致。在OpenMP中,临界区的名字是可选的,所有的临界区都是外部链接的,也就是说是他们是公共变量,是所有线程可见的,不管该线程属于哪个组。对于没有指定名字的临界区,OpenMP编译时认为所有的没有指定名字的临界区,都对应一个外部(或全局)的临界区。不同于一些常见的OpenMP结构语句,临界区结构的末尾并没有一个隐含的同步路障。临界区的代码应该是一个块结构(structured block其中不允许有返回(return), 退出(exit或者从里面跳出(go to)语句。


    展开全文
  • C++ 线程同步之临界区CRITICAL_SECTION

    万次阅读 2019-08-07 20:46:15
    程序中通常将多线程同时访问的某个资源作为临界区,需要定义一个CRITICAL_SECTION类型的变量,然后调用InitializeCriticalSection函数对变量进行初始化; 函数声明: VOID InitializeCriticalSection(LPCRITICAL_...

    一、临界区

    临界区又称关键代码段,指的是一小段代码在代码执行前,他需要独占一些资源。程序中通常将多线程同时访问的某个资源作为临界区,需要定义一个CRITICAL_SECTION类型的变量,然后调用InitializeCriticalSection函数对变量进行初始化;

    函数声明:

    VOID InitializeCriticalSection(LPCRITICAL_SECTION lpCriticalSection );

    lpCriticalSection:一个CRITICAL_SECTION结构指针,表示用于初始化的临界区;

    InitializeCriticalSection函数在内部设置了CRITICAL_SECTION结构的某些成员变量,所以他不会失败。

    为了将某一段代码定义为临界区,需要调用EnterCriticalSection函数;

    VOID WINAPI EnterCriticalSection(__inout LPCRITICAL_SECTION lpCriticalSection);

    该函数的作用是判断是否有线程访问临界区资源,如果没有,就改变CRITICAL_SECTION结构的成员变量的值,赋予当前线程访问权,函数立即返回;如果有线程正在访问资源,则进入等待状态,直到没有线程访问。

    释放资源函数:

    void WINAPI LeaveCriticalSection( _Inout_LPCRITICAL_SECTION lpCriticalSection);

    释放CRITICAL_SECTION结构指针

    void WINAPI DeleteCriticalSection(_Inout_ LPCRITICAL_SECTION lpCriticalSection);

    二、学习案例

    用三个线程同时运行将一个变量增加到30;

    临界区对象类

    #ifndef CAUTO_LOCK_H__
    #define CAUTO_LOCK_H__
    
    class CAutoLock
    {
    public:
    	CAutoLock();
    	~CAutoLock();
    
    	void Lock();
    	void UnLock();
    
    private:
    	CRITICAL_SECTION m_Section;
    };
    
    #endif
    #include "stdafx.h"
    #include "CAutoLock.h"
    
    CAutoLock::CAutoLock()
    {
    	InitializeCriticalSection(&m_Section);
    	//Lock();如果是用的时候只定义锁对象,可以不手动进入临界区和退出临界区
    }
    
    CAutoLock::~CAutoLock()
    {
    	DeleteCriticalSection(&m_Section);
    	//UnLock();
    }
    
    void CAutoLock::Lock()
    {
    	EnterCriticalSection(&m_Section);
    }
    
    void CAutoLock::UnLock()
    {
    	LeaveCriticalSection(&m_Section);
    }

    三个线程创建类

    #ifndef _TEST_CRITICAL_SECTION_H__
    #define _TEST_CRITICAL_SECTION_H__
    #include "CAutoLock.h"
    class TestCriticalSection
    {
    public:
    	TestCriticalSection();
    	~TestCriticalSection();
    
    	void StartThread();//开始线程函数
    
    	
    	static DWORD __stdcall ThreadFun1(LPVOID lParam);//线程回调函数1
    	static DWORD __stdcall ThreadFun2(LPVOID lParam);//线程回调函数2
    	static DWORD __stdcall ThreadFun3(LPVOID lParam);//线程回调函数3
    
    private:
    	HANDLE m_hThread1;
            HANDLE m_hThread2;
    	HANDLE m_hThread3;
    
    	CAutoLock m_lock;//三个线程公用的临界区锁
    
    	static int m_nTotals;
    };
    
    #endif
    #include "stdafx.h"
    #include "CCriticalSection.h"
    #include <iostream>
    using namespace std;
    
    int TestCriticalSection::m_nTotals = 0;//初始化静态成员变量
    
    TestCriticalSection::TestCriticalSection()
    {
    	m_nTotals = 0;
            m_hThread1 = INVALID_HANDLE_VALUE;
    	m_hThread2 = INVALID_HANDLE_VALUE;
    }
    
    TestCriticalSection::~TestCriticalSection()
    {
    	if (m_hThread1 != NULL)
    	{
    		CloseHandle(m_hThread1);
    		m_hThread1 = NULL;
    	}
    
    	if (m_hThread2 != NULL)
    	{
    		CloseHandle(m_hThread2);
    		m_hThread2 = NULL;
    	}
    
    	if (m_hThread3 != NULL)
    	{
    		CloseHandle(m_hThread3);
    		m_hThread3 = NULL;
    	}
    }
    
    DWORD __stdcall TestCriticalSection::ThreadFun1(LPVOID lParam) //static只需要加在类定义里,类定义外面的函数定义前不能写static
    {
    	DWORD dRet = TRUE;
    	TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
    	while(1)
    	{
    		pThis->m_lock.Lock();
    		pThis->m_nTotals ++;
    		cout<<"ThreadFun1: m_nTotals "<<pThis->m_nTotals<<endl;
    		
    		if (pThis->m_nTotals >= 30)
    		{
    			break;
    		}
                    pThis->m_lock.UnLock();
    		Sleep(10);
    	}
    
    	return dRet;
    }
    
    DWORD __stdcall TestCriticalSection::ThreadFun2(LPVOID lParam)
    {
    	DWORD dRet = TRUE;
    	TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
    	while(1)
    	{
    		pThis->m_lock.Lock();
    		pThis->m_nTotals ++;
    		cout<<"ThreadFun2: m_nTotals "<<pThis->m_nTotals<<endl;
    		
    		if (pThis->m_nTotals >= 30)
    		{
    			break;
    		}
                    pThis->m_lock.UnLock();
    		Sleep(10);
    	}
    	return dRet;
    }
    
    DWORD __stdcall TestCriticalSection::ThreadFun3(LPVOID lParam)
    {
    	DWORD dRet = TRUE;
    	TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
    	while(1)
    	{
    		pThis->m_lock.Lock();
    		pThis->m_nTotals ++;
    		cout<<"ThreadFun3: m_nTotals "<<pThis->m_nTotals<<endl;
    		
    		if (pThis->m_nTotals >= 30)
    		{
    			break;
    		}
                    pThis->m_lock.UnLock();
    		Sleep(10);
    	}
    
    	return dRet;
    }
    
    void TestCriticalSection::StartThread()
    {
    	m_hThread1 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun1, this,  0, NULL);
    	m_hThread2 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun2, this, 0, NULL);
    	m_hThread3 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun3, this, 0, NULL);
    }

    主函数:

    // CriticalSection.cpp : 定义控制台应用程序的入口点。
    //
    
    #include "stdafx.h"
    #include "CCriticalSection.h"
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    	TestCriticalSection  CriticalSectionObj;
    	CriticalSectionObj.StartThread();
    	Sleep(5000);
    	system("pause");
    	return 0;
    }
    
    

    结果:

     

    如果代码改成下面这样,不加临界区;资源访问会冲突

    #include "stdafx.h"
    #include "CCriticalSection.h"
    #include <iostream>
    using namespace std;
    
    int TestCriticalSection::m_nTotals = 0;
    
    TestCriticalSection::TestCriticalSection()
    {
    	m_nTotals = 0;
        m_hThread1 = INVALID_HANDLE_VALUE;
    	m_hThread2 = INVALID_HANDLE_VALUE;
    }
    
    TestCriticalSection::~TestCriticalSection()
    {
    	if (m_hThread1 != NULL)
    	{
    		CloseHandle(m_hThread1);
    		m_hThread1 = NULL;
    	}
    
    	if (m_hThread2 != NULL)
    	{
    		CloseHandle(m_hThread2);
    		m_hThread2 = NULL;
    	}
    
    	if (m_hThread3 != NULL)
    	{
    		CloseHandle(m_hThread3);
    		m_hThread3 = NULL;
    	}
    }
    
    DWORD __stdcall TestCriticalSection::ThreadFun1(LPVOID lParam) //static只需要加在类定义里,类定义外面的函数定义前不能写static
    {
    	DWORD dRet = TRUE;
    	TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
    	while(1)
    	{
    		//pThis->m_lock.Lock();
    		pThis->m_nTotals ++;
    		cout<<"ThreadFun1: m_nTotals "<<pThis->m_nTotals<<endl;
    		//pThis->m_lock.UnLock();
    		Sleep(10);
    		if (pThis->m_nTotals == 30)
    		{
    			break;
    		}
    	}
    
    	return dRet;
    }
    
    DWORD __stdcall TestCriticalSection::ThreadFun2(LPVOID lParam)
    {
    	DWORD dRet = TRUE;
    	TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
    	while(1)
    	{
    		//pThis->m_lock.Lock();
    		pThis->m_nTotals ++;
    		cout<<"ThreadFun2: m_nTotals "<<pThis->m_nTotals<<endl;
    		//pThis->m_lock.UnLock();
    		Sleep(10);
    		if (pThis->m_nTotals == 30)
    		{
    			break;
    		}
    	}
    	return dRet;
    }
    
    DWORD __stdcall TestCriticalSection::ThreadFun3(LPVOID lParam)
    {
    	DWORD dRet = TRUE;
    	TestCriticalSection * pThis = static_cast<TestCriticalSection*>(lParam);
    	while(1)
    	{
    		//pThis->m_lock.Lock();
    		pThis->m_nTotals ++;
    		cout<<"ThreadFun3: m_nTotals "<<pThis->m_nTotals<<endl;
    		//pThis->m_lock.UnLock();
    		Sleep(10);
    		if (pThis->m_nTotals == 30)
    		{
    			break;
    		}
    	}
    
    	return dRet;
    }
    
    void TestCriticalSection::StartThread()
    {
    	m_hThread1 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun1, this,  0, NULL);
    	m_hThread2 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun2, this, 0, NULL);
    	m_hThread3 = CreateThread(NULL, 0, &TestCriticalSection::ThreadFun3, this, 0, NULL);
    }

    结果可能会出现下面这种状况

     

     

     

     

     

    展开全文
  • 对于CRITICAL_SECTION用法的介绍和理解

    万次阅读 多人点赞 2018-08-27 16:29:40
     很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION是锁定了资源,其实,CRITICAL_SECTION是不能够“锁定”资源的,它能够完成的功能,是同步不同线程的代码段。简单说,当一个线程执行了...
  • 学习总结_Week1

    2020-08-09 18:42:11
    8:00–9:00 AM 9:00–11:30 AM 基数天阅读,偶数天听力 网安学习 6:30–7:00 PM 11:30 PM 总结反思 睡觉 这是本周的学习时间表,总时间不长,所以应当加强学习效率。 不足 经常性晚睡,导致第二天要么......
  • 临时解决方案: 禁用数字签名就行了,但为了下次重启不用再关闭数签名这玩意;以管理员身份进入到CMD,执行bcdedit.exe /set nointegritychecks on关闭数字签名,然后再关闭计算机(不是重启); 有的机型会出现出现此...
  • 命令行输入 mdsched.exe 重启 卸载显卡驱动,重新安装 重启
  • configure 时出现以下错误:checking for ...*** These critical programs are missing or too old: as ld *** Check the INSTALL file for required versions.问题原因: 缺少autoconf或版本太低下载安装新版本#
  • OS_ENTER_CRITICAL()和OS_EXIT_CRITICAL()

    万次阅读 2015-12-14 21:26:30
    这两个函数是uC/OS操作系统进入/退出“临界区”的功能代码,一般格式为: ... OS_ENTER_CRITICAL(); //(2)  .............. .(4) ............. OS_EXIT_CRITICAL(); ///(3) 其中OS_ENTER_CRITI
  • 1.硬件驱动问题 Win +X打开设备管理器-扫描检测硬件改动 看是否有硬件驱动问题(黄色标识),有的话更新或卸载重装 2.查看蓝屏原因 使用软件:BlueScreenView ...window日志-系统:红色错误标识为系统错误问题 ...
  • 最近自己的win10系统出现了critical structure corruption的蓝屏报错,隔基本一小时左右就需要重新启动一次,本来是想要重装系统,但是学软件的,重装系统极其麻烦,便是摸索出一条解决方法,希望对大家有帮助。...
  • let src ="../../assets/imgs/ziyuan6.png"; require(src); require接收了一个变量,会报上面的警告,接收一个写死的字符串值时则没有警告! 下面三种写法没有报警告: ...require("../../assets/imgs/ziyuan6.png")...
  • 今天下午编译glibc2.9,一直出现These critical programs are missing or too old: as ld 错误,上网查一下,一般是说These critical programs are missing or too old:后面指示的是后面的软件的版本太低或者太高,...
  • uCOS-III是uCOS-II的升级版本,虽然针对于后者有各种优化,但是有一点不是特别好,就是有些宏定义名称会做一些修改,这种修改可能更合理,但是可能会引起一些不必要的“误会 ”,例如在uCOS-II中临界区的使用函数...
  • 显示0190crltical low-battery error(电池电量严重不足错误)的消息之后,计算机立即...从Critical low-battery error 我们可以知道是电量过低的意思,这下可以把维修范围缩小了,应该是充电部分的电路有问题。* ...
  • windows10 critical_service_failed解决办法

    万次阅读 2019-08-26 21:44:28
    电脑声卡貌似坏了 不出声 插了usb声卡后再重启便有这个错误 蓝屏 无限重启 首先要开机 出现错误提示后系统会收集信息然后再次启动,如此反复2次之后启动修复程序, 此时选择疑难解答 - 高级选项 - 启动设置 -...
  • 具体场景出现在new分配内存中,VS输出信息为: Critical error detected c0000374.也就是堆管理器尝试在0xc0000374这个地址分配内存,但是这里是内核模式地址区域,堆管理器是不可能指定这个地址的.所以很明显,堆数据被...
  • OS_CRITICAL_METHOD的意思

    千次阅读 2012-03-05 11:03:24
    在UCOS2的源代码及相关应用程序中,总会出现OS_CRITICAL_METHOD ==3的判断,为此在网上找到该语句的解释, Method #1: Disable/Enable interrupts using simple instructions. After critical section, interrupts ...
  • 解决方法一:  这个问题可能是你的pywin32版本不太对,无论你的电脑是32位还是64位,你要安装的pywin32一定要与你Python的版本相一致, 如果Python是32就从Python扩展包 下载32位的,是64就下载64的 ...
1 2 3 4 5 ... 20
收藏数 110,278
精华内容 44,111
关键字:

critical