热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

《WindowsviaC/C++》学习笔记——用户模式的“线程同步”之“关键代码段”

上一篇讨论有关“互锁函数家族”处理线程同步的方法。本书中还描述了“高速缓存行”(cacheline)概念,还给了一些线程同步的建议

  上一篇讨论有关“互锁函数家族”处理线程同步的方法。本书中还描述了“高速缓存行”(cache line)概念,还给了一些线程同步的建议,比如要实现单个变量的同步,应该避免使用volatile关键字,如果实在需要对单个变量进行同步,最好使用互锁函数(传递的是地址,所以每次取值都从内存取得)。感觉这些东西没有什么好讲的,看看书就可以了。

  然后,本书提供了另外一种工作在用户模式的线程同步的方法:关键代码段。虽然不能协调多个进程中的线程,但是我确实最经常使用这种机制来协调单个进程中线程的同步(因为好用^_^)。

 

  关键代码段,所谓“代码段”,也就是说“一段代码”。这段代码的执行是以原子的形式执行的,独占着某些资源,任何想要访问这些资源的其他线程在关键代码段执行完成之前只能等待。

 

  要让实现关键代码段,需要以下5个步骤:

1、初始化一个关键代码段结构。

2、一个线程进入关键代码段。

3、对资源进行原子操作。

4、该线程离开关键代码段。

5、删除关键代码段。

 

  上述5个步骤,除了第3步是程序员需要自己进行设计,其他都有相应的API函数可以被调用。

  首先看下关键代码段结构:CRITICAL_SECTION。这个数据结构是有明确文档定义的,但是微软认为里面的内容不需要我们去了解。所以该结构内部的成员对我们来说是透明的。该结构在WinBase.h文件中被定义为RTL_CRITICAL_SECTION。

 

  在第1步,你需要初始化这个结构,调用InitializeCriticalSection函数,传递一个该结构的指针。在该函数内部会设置CRITICAL_SECTION的内部成员。

VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);

 

  在第5步,当你不再需要这个关键代码段的时候,你需要呼叫函数DeleteCriticalSection来清除CIRTICLA_SECTION结构,同样是传递一个该结构的指针。

VOID InitializeCriticalSection(PCRITICAL_SECTION pcs);

 

  当你调用InitializeCriticalSection函数初始化了一个关键代码段之后,你就可以让你的线程通过这个关键代码段来对某写资源进行原子访问。

  在第2步,你需要进入一个关键代码段,呼叫函数EnterCriticalSection,同样传递一个已经初始化了的CRITICAL_SECTION结构指针

VOID EnterCriticalSection(PCRITICAL_SECTION pcs);

 

  当调用该函数的时候,会发生以下三种的处理:

  如果没有其他线程在访问相关资源,那么该函数更新内部数据,指明当前线程已经被赋予访问权并立即返回,使得该线程可以继续执行。

  如果CRITICAL_SECTION结构的成员变量指明了当前线程已经被赋予了访问权,则更新内部数据,指明该线程被赋予了多少次访问权(递增计数)。这种情况比较少见,只有在一个线程内部多次调用EnterCriticalSection函数才会发生。

  如果CRITICAL_SECTION结构的成员变量指明了当前已经有一个线程被赋予了访问权,那么该函数将当前线程设置为等待状态。然后更新内部数据,一旦正在访问资源的线程离开的关键代码段(调用LeaveCritlcalSection,后面会讲),该线程就会处于可调度状态。  

 

  如果当前已经初始化了一个关键代码段cs,同时存在着2个线程:T1和T2。然后T1呼叫EnterCriticalSection(&cs)函数进入该关键代码段,然后T2也呼叫EnterCriticalSection(&cs),那么T2会进入等待状态,等待T1离开cs所代表的关键代码段后,T2才恢复到可调度状态。

  实际上,等待的线程可能会超时,然后抛出一个异常。该时间数值由注册表中的一个值表示的:

HKEY_LOCAL_MACHINE\System\CurrentControlSet\Control\Session Manager

  该值默认为2592000s,即大约3 0天。

 

  从内部来讲,EnterCriticalSection函数并不复杂,在内部使用互锁函数,执行的只是一些简单的测试。但是这些测试都是以原子的方式进行的。

 

  你可以使用函数TryEnterCriticalSection来代替EnterCriticalSection。

BOOL TryEnterCriticalSection(PCRITICAL_SECTION pcs);

 

  该函数不会让呼叫的线程进入等待状态,相反,该函数返回一个BOOL变量,指明当前线程能否进入关键代码段。

  该函数可以让线程快速地查看能否获取某些资源,如果不能,该线程可以继续做其他的事情。如果该函数返回TRUE,说明CRITICAL_SECTION的成员变量已经更新,可以进入对应的关键代码段了。

  如果一个线程顺利地进入了关键代码段,那么意味着它可以独占某些资源,此时可以以特定的算法来对某些资源访问和操作了(对应步骤3)。

 

  第4步,在一个线程对某些资源访问或操作完成之后,必须离开关键代码段,调用函数LeaveCriticalSection函数,同样传递一个CRITICAL_SECTION结构的指针。

VOID LeaveCriticalSection(PCRITICAL_SECTION pcs);

 

  调用该函数的时候,CRITICAL_SECTION的内部数据会被更新。该函数使计数递减1,指明当前线程被赋予的访问权次数。

  如果递减后,该计数仍然大于0,则该函数什么也不做,只是简单的返回而已。

  递减后,如果该计数等于0,它就更新成员变量,并查看那些因为调用EnterCriticalSection而处于等待状态的线程,如果存在这样的线程,它就更新成员变量,并选择其中一个,让其变成可调度状态;如果没有线程在等待,则更新成员变量,说明此时没有线程在访问资源。

  也就是说,一个EnterCriticalSection函数必须有一个LeaveCriticalSection函数与之对应,否则一个线程会一直独占了某些资源,即使该线程结束之后,这些资源也被关键代码段锁定。而其他线程如果调用EnterCriticalSection进入该关键代码段是无法访问这些资源的。

 

 

下面讨论有关“关键代码段与循环锁”的问题。

 

  如果一个关键代码段已经被其他线程所拥有,那么如果当前线程试图进入这个关键代码段的时候,会立即被设置为等待状态。意味着该线程必须从用户模式转入内核模式,大约需要1000个CPU周期。这种转换是需要付出代价的。实际上,在多CPU计算机上,当前拥有资源的线程可能正执行在另一个CPU上,这样,它很可能会马上离开关键代码段,释放相关资源。

  为了提高关键代码段的性能,微软为关键代码段提供了循环锁机制。当一个线程调用EnterCriticalSection函数的时候,可以使用循环锁进行循环查询,这样就可以多次尝试访问资源。只有当每次尝试均告失败之后,该线程才转入内核模式。

  如此一来,只要在这组尝试失败以前,原先占有资源的线程离开了关键代码段,那么该尝试访问资源的线程便可尝试成功,这样避免了转入内核模式的执行,提高的性能。

  要将循环锁用于关键代码段,必须将一个关键代码段与一个循环次数关联起来,可以调用InitializeCriticalSectionAndSpinCount函数,即能够初始化关键代码段,也可以将一个循环锁查询次数与之绑定。

BOOL InitializeCriticalSectionAndSpinCount(
   PCRITICAL_SECTION pcs,     
//关键代码段结构指针
   DWORD dwSpinCount);        //循环锁循环查询次数(尝试访问资源次数)

 

  要注意的情况是,如果在单CPU的计算机上,该函数的第二个参数dwSpinCount会被忽略,永远为0,因为在单CPU上,如果一个线程在循环尝试请求资源,而当前拥有资源的线程不可能被调度,资源是无法释放的。

  也可以修改一个关键代码段循环锁循环次数:

DWORD SetCriticalSectionSpinCount(
   PCRITICAL_SECTION pcs,    
//关键代码段结构指针
   DWORD dwSpinCount);       //循环锁循环次数

 

  当然,如果运行在单CPU计算机上,dwSpinCount参数会被忽略。

  一般地,经验告诉我们,设置dwSpinCount为4000,即让线程循环4000次来尝试获取资源。

 

  InitializeCriticalSection函数可能会运行失败(在资源极度贫乏的情况下),由于微软忽略了这个问题,所以它的返回类型是VOID。在这种情况下,你可以使用InitializeCriticalSectionAndSpinCount函数,它返回一个BOOL型数据,指明初始化关键代码段是否成功。

 

  当使用关键代码段的时候,可能会出现对关键代码段的争用,即当前线程调用EnterCriticalSection函数的时候,该关键代码段的访问权已经被另一个线程所拥有,此时发生了争用。此时关键代码段使用事件内核对象处理线程同步问题。

  当在内存资源极度贫乏的情况下,此时线程争用关键代码段,那么关键代码段可能无法创建必要的事件内核对象,这个时候EnterCriticalSection函数会产生一个EXCEPTION_INVALID_HANDLE异常,你可以采取一下两种方法处理之:

1、使用结构化异常的方法,当异常产生的时候,不访问关键代码段保护的资源,当内存变成可用状态的时候,再次呼叫EnterCriticalSection函数。

2、使用InitializeCriticalSectionAndSpinCount函数创建关键代码段的时候确保设置了dwSpinCount参数的高信息位,当该函数发现dwSpinCount的高信息位被设置,它会创建一个事件内核对象,并将该内核对象与关键代码段关联起来。如果事件内核对象无法创建,函数返回FALSE。如果创建成功那么就意味着EnterCriticalSection总能运行成功,因为总是先创建事件内核对象。

 

转:https://www.cnblogs.com/wz19860913/archive/2008/08/07/1262765.html



推荐阅读
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
  • Windows下配置PHP5.6的方法及注意事项
    本文介绍了在Windows系统下配置PHP5.6的步骤及注意事项,包括下载PHP5.6、解压并配置IIS、添加模块映射、测试等。同时提供了一些常见问题的解决方法,如下载缺失的msvcr110.dll文件等。通过本文的指导,读者可以轻松地在Windows系统下配置PHP5.6,并解决一些常见的配置问题。 ... [详细]
  • vue使用
    关键词: ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 原文地址:https:www.cnblogs.combaoyipSpringBoot_YML.html1.在springboot中,有两种配置文件,一种 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • XML介绍与使用的概述及标签规则
    本文介绍了XML的基本概念和用途,包括XML的可扩展性和标签的自定义特性。同时还详细解释了XML标签的规则,包括标签的尖括号和合法标识符的组成,标签必须成对出现的原则以及特殊标签的使用方法。通过本文的阅读,读者可以对XML的基本知识有一个全面的了解。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
  • 阿里Treebased Deep Match(TDM) 学习笔记及技术发展回顾
    本文介绍了阿里Treebased Deep Match(TDM)的学习笔记,同时回顾了工业界技术发展的几代演进。从基于统计的启发式规则方法到基于内积模型的向量检索方法,再到引入复杂深度学习模型的下一代匹配技术。文章详细解释了基于统计的启发式规则方法和基于内积模型的向量检索方法的原理和应用,并介绍了TDM的背景和优势。最后,文章提到了向量距离和基于向量聚类的索引结构对于加速匹配效率的作用。本文对于理解TDM的学习过程和了解匹配技术的发展具有重要意义。 ... [详细]
author-avatar
蓝瑟
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有