使用临界区对象(CriticalSection)需要注意的一些事情

2013年10月28日 ⁄ 综合 ⁄ 共 2591字 ⁄ 字号 小 中 大 ⁄ 评论关闭

1. 临界区对象不是内核对象,因此不能继承,不能跨进程,也不能用waitfor什么的函数来限定时间等待。这个很好理解,你想想WaitFor要求传一个句柄,而临界区对象的类型都不是句柄,也不能用CloseHandle来关闭,怎么可能会能让WaitForXXX搞了。

2. 临界区对象使用前必须初始化,不初始化会崩溃,这是我的亲历。

3. 线程进入临界区之前会先自旋那么几次,所有自旋锁都失败了之后会创建一个内核对象然后等待这个内核从而进入到内核模式。

4. Enter和Leave必须匹配,每Enter一次,就得Leave一次,这又是可恶的计数机制。参见下面的代码:

typedef struct _RTL_CRITICAL_SECTION {PRTL_CRITICAL_SECTION_DEBUG DebugInfo;////  The following three fields control entering and exiting the critical//  section for the resource//LONG LockCount;LONG RecursionCount;HANDLE OwningThread;        // from the thread's ClientId->UniqueThreadHANDLE LockSemaphore;ULONG_PTR SpinCount;        // force size on 64-bit systems when packed
} RTL_CRITICAL_SECTION, *PRTL_CRITICAL_SECTION;

这是临界区对象的定义,看见

RecursionCount

这个对象了吧,你觉得它能干点啥?同时在这里你还能看到一个信号量的内核对象,还有一个自旋数量。这些玩意印证了上面的话。如果你同一个线程Leave之前Enter了两次,必须调用两个Leave,不然这个临界区对象依然会阻塞别的线程。再不明白就去看我前面有关挂起线程的那个博文。

5. 由于进入临界去是无限等待的,因此你有时间肯定希望有种方法能够查看一下临界区是否可用,不可用则希望线程立刻去做其它的事情。这时候,你就需要一个TryEnterCriticalSectionAPI,这玩意很好理解,你踹一脚临界区,如果能进去就进去,不能进去这个API立刻以False返回,你就可以安排线程去做其它的事情。注意:你一脚踹进去了之后完事了记得要离开(LeaveCriticalSection)。

6. 前面说了,临界区真正用内核对象挂起线程之前会自旋好几次,因此你看对象里就有一个自旋锁的计数。你可以改这个自旋锁的数量。当然我不是说让你直接修改对象的成员变量!你可以在初始化的时候指定自旋锁的数量,用这个API:InitializeCriticalSectionAndSpinCount。在这里小说一下临界区为什么会自旋。因为程序从用户态转到内核模式需要昂贵的开销(大概数百个CPU周期),很多情况下,A线程还没完成从用户态转到内核态的操作呢,B线程就已经释放资源了。于是临界区就先隔一段时间自旋一次,直到所有自旋次数都耗尽,就创建个内核对象然后挂起线程。但是,如果您的机器只有一个CPU,那么这个自旋次数就没用了,操作系统直接会无视它。原因如下:你自旋着呢,操作B线程释放不了资源,于是你还不如直接切入等待状态让B来释放资源。动态更改自旋数量请使用SetCriticalSectionSpinCount,别做直接更改对象成员变量的二事!

7. 最后,初始化临界区和进入临界区的时候都有可能会遇到异常状况,比如初始化的时候需要申请一些内存来保存DEBUG的信息(参见上面代码的第一个成员变量),如果内存不够,初始化就崩了。进入临界区的时候可能需要新建一个内核对象,如果内存不够,也崩了。解决这个问题的方法有两种

  1. 结构化异常处理
  2. 初始化的时候使用InitializeCriticalSectionAndSpinCount。这个API有返回值,如果它申请DEBUG信息失败,返回FALSE,另外,刚才提到了这个API可以指定自旋锁的自旋次数。这个自旋次数的范围并非是用0到0xFFFF
    FFFF而是0--->0x00FF FFFF,因此你可以设定高位为1指示初始化的时候直接建立内核对象。如果建立失败,这个函数也会调用失败。当然了,一上来就搞一个内核对象似乎有点浪费内存,但是这样能够保证进入临界区不会失败!但是吧,你需要注意,设置高位来保证内核对象的创建只能在2000上玩。MSDN上有说明,不信你看:
  3. Windows  2000:  If the high-order bit is set, the function pre-allocates the event used by theEnterCriticalSection
    function. Pre-allocation guarantees that entering or leaving the critical section will not raise an exception in low memory conditions. Do not set this bit if you are creating a large number of critical section objects, because it consumes a significant amount
    of nonpaged pool. Note that this event is allocated on demand starting with Windows XP and the high-order bit is ignored.

最后是一些实验:

我们看看用InitializeCriticalSection初始化一个临界区对象后,这些成员变量(除去DEBUG外)都是什么样子。

我们Enter一下,看看会变成什么样子

我们再让其它线程也Enter一下看看

可见,新建了一个内核对象。

我们现在让主线程退出临界区

对照线程句柄我们可以看出第二个线程获得了临界区对象。

我们再让第二个线程退出临界区。

临界区除去内核对象外回到了原始状态。

实验2:我们让临界区对象在同一线程内相继被进入两次

::EnterCriticalSection(&g_cs);
::EnterCriticalSection(&g_cs);

可见,计数增加了一个,变成了,因此你得leave两次才能开锁

转自:https://www.xuebuyuan.com/1304408.html

使用临界区对象(CriticalSection)需要注意的一些事情相关推荐

  1. 线程同步 -事件Event、临界区对象CriticalSection

    事件Event: 基本函数: 全局对象:HANDLE g_hEvent 创建事件对象:g_hEvent=CreateEvent(NULL,FALSE,FALSE,NULL) 重置状态为无信号:Rese ...

  2. C++ 线程同步之临界区CRITICAL_SECTION

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

  3. 多线程(C++)临界区Critical Sections

    一 .Critical Sections(功能与Mutex相同,保证某一时刻只有一个线程能够访问共享资源,但是不是内核对象,所以访问速度比Mutex快,但是没有等待超时的功能,所以有可能导致死锁,使用 ...

  4. linux临界区原理,临界区的实现原理

    临界区概述: 用于多线程的互斥访问.如果有多个线程试图同时访问临界区,那么在有一个线程进入临界区后,其他试图访问的线程将被挂起,直到进入临界区的线程离开.临界区在被释放后,其他线程可以继续抢占,并以此 ...

  5. 临界区锁 InitializeCriticalSection()--- EnterCriticalSection()--LeaveCriticalSection()

    1. InitializeCriticalSection 此函数初始化一个临界区对象. 格式: void InitializeCriticalSection(  LPCRITICAL_SECTION  ...

  6. 操作系统:临界资源与临界区的区别

    1.不论是硬件临界资源,还是软件临界资源,多个进程必须互斥地对它进行访问.每个进程中访问临界资源的那段代码称为临界区(CriticalSection). 每个进程中访问临界资源的那段程序称为临界区(C ...

  7. Windows编程-- 用户方式中线程的同步---关键代码段(临界区)

    可以从例子学习,更好的掌握 #include <windows.h> #include <iostream.h> //两个线程的声明 DWORD WINAPI Fun1Proc ...

  8. 临界区,互斥量,信号量,事件的区别

    临界区(Critical section)与互斥体(Mutex)的区别 1.临界区只能用于对象在同一进程里线程间的互斥访问:互斥体可以用于对象进程间或线程间的互斥访问. 2.临界区是非内核对象,只在用 ...

  9. 线程同步(临界区、互斥量、事件、信号量)

    1.为什么线程要同步? #include<windows.h> #include<iostream> using namespace std; DWORD WINAPI Thr ...

最新文章

  1. 算法-数组中重复的数字
  2. python两数相加取_Leetcode_两数相加_Python
  3. 笔记-信息系统安全管理-信息安全(混合)
  4. ft232r usb uart驱动 win7_新电脑想装WIN7,技术员让装WIN10,不是忽悠你,是有原因的...
  5. 《毅力–如何培养自律的习惯》读书笔记
  6. JQuery七个常犯的错误
  7. Win10 Terminal + WSL 2 安装配置指南
  8. linux的文件权限前面的东西,linux 文件权限解析
  9. mysql tee_MySQL 使用tee记录语句和输出日志
  10. 《Head First设计模式》第二版中译本内文彩页欣赏
  11. 矩阵乘法(幂次计算)
  12. JavaScript基础知识-JS数据类型
  13. 欢迎进入徐松亮博客一站式导航搜索(随时更新)
  14. UFS系列三:UFS数据包UPIU
  15. 基金投资基本常识【狂神说】
  16. ECMAScript标准简介
  17. 在线识别图片文字,分享识别技巧
  18. 卸载oracle apex,oracle关闭apex的jobs
  19. 轻巧入耳,畅享高音质,雷柏VM700S蓝牙TWS背光游戏耳机开箱实测
  20. 适配器模式(对象适配器)

热门文章

  1. 线段树(单点修改,区间查询)
  2. 如何打造“百万美金直播间”?教你几招跨境直播秘诀。
  3. JAVA互联网架构之Spring学习其一配置bean及工厂
  4. 卡尔曼滤波分析及程序
  5. 安全扫描失败无法上传_教你3招解决PDF文字无法复制的所有问题
  6. 月薪3000和30000的数据分析师差在哪?
  7. 第一时间体验谷歌词霸的魅力(多图)
  8. python接口自动化(十七) requests获取响应时间(elapsed)与超时(timeout)
  9. 一篇弄懂 offsetWidth、offsetHeight、offsetleft、offsetTop和offsetParent的区别!(1)
  10. facebook,twitter的分享图片功能,facebook分享图片