多线程(C++)临界区Critical Sections问题

2023-11-13

多线程中用来确保同一时刻只有一个线程操作被保护的数据

InitializeCriticalSection(&cs);//初始化临界区 

EnterCriticalSection(&cs);//进入临界区 

//操作数据 

MyMoney*=10;//所有访问MyMoney变量的程序都需要这样写Enter…
Leave… 

LeaveCriticalSection(&cs);//离开临界区 

DeleteCriticalSection(&cs);//删除临界区

多个线程操作相同的数据时,一般是需要按顺序访问的,否则会引导数据错乱,无法控制数据,变成随机变量。为解决这个问题,就需要引入互斥变量,让每个线程都按顺序地访问变量。这样就需要使用EnterCriticalSection和

LeaveCriticalSection函数。

 

比如说我们定义了一个共享资源dwTime[100],两个线程ThreadFuncA和ThreadFuncB都对它进行读写操作。当我们想要保证 dwTime[100]的操作完整性,即不希望写到一半的数据被另一个线程读取,那么用CRITICAL_SECTION来进行线程同

步如下:

第一个线程函数:

DWORD   WINAPI   ThreadFuncA(LPVOID   lp) 

EnterCriticalSection(&cs); 

… 

//  
操作dwTime 

… 

LeaveCriticalSection(&cs); 

return  
0; 

}

写出这个函数之后,很多初学者都会错误地以为,此时cs对dwTime进行了锁定操作,dwTime处于cs的保护之中。一个“自然而然”的想法就是——cs和dwTime一一对应上了。

这么想,就大错特错了。dwTime并没有和任何东西对应,它仍然是任何其它线程都可以访问的。如果你像如下的方式来写第二个线程,那么就会有问题:

DWORD   WINAPI   ThreadFuncB(LPVOID   lp) 

... 
//   操作dwTime 
... 
return   0; 
}

当线程ThreadFuncA执行了EnterCriticalSection(&cs),并开始操作dwTime[100]的时候,线程 ThreadFuncB可能随时醒过来,也开始操作dwTime[100],这样,dwTime[100]中的数据就被破坏了。

为了让CRITICAL_SECTION发挥作用,我们必须在访问dwTime的任何一个地方都加上 EnterCriticalSection(&cs)和LeaveCriticalSection(&cs)语句。所以,必须按照下面的 方式来写第二个线程函数:

DWORD   WINAPI   ThreadFuncB(LPVOID   lp) 

EnterCriticalSection(&cs); 

… 

//  
操作dwTime 

… 

LeaveCriticalSection(&cs); 

return  
0; 

}

这样,当线程ThreadFuncB醒过来时,它遇到的第一个语句是EnterCriticalSection(&cs),这个语句将对cs变量 进行访问。如果这个时候第一个线程仍然在操作dwTime[100],cs变量中包含的值将告诉第二个线程,已有其它线程占用了

cs。因此,第二个线程的 EnterCriticalSection(&cs)语句将不会返回,而处于挂起等待状态。直到第一个线程执行了 LeaveCriticalSection(&cs),第二个线程的EnterCriticalSection(&cs)语句才会返回, 并且继续执行下面的操作

这个过程实际上是通过限制有且只有一个函数进入CriticalSection变量来实现代码段同步的。简单地说,对于同一个 CRITICAL_SECTION,当一个线程执行了EnterCriticalSection而没有执行LeaveCriticalSection的时 候,其它任何一

个线程都无法完全执行EnterCriticalSection而不得不处于等待状态。

再次强调一次,没有任何资源被“锁定”,CRITICAL_SECTION这个东东不是针对于资源的,而是针对于不同线程间的代码段的!我们能够用它来进 行所谓资源的“锁定”,其实是因为我们在任何访问共享资源的地方都加入了

EnterCriticalSection和 LeaveCriticalSection语句,使得同一时间只能够有一个线程的代码段访问到该共享资源而已(其它想访问该资源的代码段不得不等待)。

这就是使用一个CRITICAL_SECTION时的情况。你应该要知道,它并没有什么可以同步的资源的“集合”。这个概念不正确。

如果是两个CRITICAL_SECTION,就以此类推。


***************************************************

     很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION是锁定了资源,其实,CRITICAL_SECTION是不能够“锁定”资源的,它能够完成的功能,是同步不同线程的代码段。简单说,当一个线程执行了

EnterCritialSection之后,cs里面的信息便被修改了,以指明哪一个线程占用了它。而此时,并没有任何资源被“锁定”。不管什么资源,其它线程都还是可以访问的(当然,执行的结果可能是错误的)。

    
只不过,在这个线程尚未执行LeaveCriticalSection之前,其它线程碰到EnterCritialSection语句的话,就会处于等待状态,相当于线程被挂起了。
这种情况下,就起到了保护共享资源的作用。 

     
也正由于CRITICAL_SECTION是这样发挥作用的,所以,必须把每一个线程中访问共享资源的语句都放在EnterCritialSection和LeaveCriticalSection之间。这是初学者很容易忽略的地方。 

当然,上面说的都是对于同一个CRITICAL_SECTION而言的。
如果用到两个CRITICAL_SECTION,比如说: 

第一个线程已经执行了EnterCriticalSection(&cs)并且还没有执行LeaveCriticalSection(&cs),这时另一个线程想要执行EnterCriticalSection(&cs2),这种情况是可以的(除非cs2已经被第三个线程抢先占用了)。
这也就是多个

CRITICAL_SECTION实现同步的思想。

       比如说我们定义了一个共享资源dwTime[100],两个线程ThreadFuncA和ThreadFuncB都对它进行读写操作。当我们想要保证dwTime[100]的操作完整性,即不希望写到一半的数据被另一个线程读取,那么用CRITICAL_SECTION来进行

线程同步如下: 
第一个线程函数: 
DWORD   WINAPI   ThreadFuncA(LPVOID   lp) 

            EnterCriticalSection(&cs); 

           
… 

           
//  
操作dwTime 

           
… 

           
LeaveCriticalSection(&cs); 

           
return  
0; 



写出这个函数之后,很多初学者都会错误地以为,此时cs对dwTime进行了锁定操作,dwTime处于cs的保护之中。一个“自然而然”的想法就是——cs和dwTime一一对应上了。 

这么想,就大错特错了。dwTime并没有和任何东西对应,它仍然是任何其它线程都可以访问的。

如果你像如下的方式来写第二个线程,那么就会有问题: 
DWORD   WINAPI   ThreadFuncB(LPVOID   lp) 

            ... 
            //   操作dwTime 
            ... 
            return   0; 

当线程ThreadFuncA执行了EnterCriticalSection(&cs),并开始操作dwTime[100]的时候,线程ThreadFuncB可能随时醒过来,也开始操作dwTime[100],

这样,dwTime[100]中的数据就被破坏了。 

     
为了让CRITICAL_SECTION发挥作用,我们必须在访问dwTime的任何一个地方都加上EnterCriticalSection(&cs)和LeaveCriticalSection(&cs)语句。

     
所以,必须按照下面的方式来写第二个线程函数: 

DWORD  
WINAPI  
ThreadFuncB(LPVOID  
lp) 



           
EnterCriticalSection(&cs); 

           
… 

           
//  
操作dwTime 

           
… 

           
LeaveCriticalSection(&cs); 

           
return  
0; 



      
这样,当线程ThreadFuncB醒过来时,它遇到的第一个语句是EnterCriticalSection(&cs),这个语句将对cs变量进行访问。

      
如果这个时候第一个线程仍然在操作dwTime[100],cs变量中包含的值将告诉第二个线程,已有其它线程占用了cs。

      
因此,第二个线程的EnterCriticalSection(&cs)语句将不会返回,而处于挂起等待状态。直到第一个线程执行了LeaveCriticalSection(&cs),

      
第二个线程的EnterCriticalSection(&cs)语句才会返回,并且继续执行下面的操作。 

       
这个过程实际上是通过限制有且只有一个函数进入CriticalSection变量来实现代码段同步的。简单地说,对于同一个CRITICAL_SECTION,

       
当一个线程执行了EnterCriticalSection而没有执行LeaveCriticalSection的时候,其它任何一个线程都无法完全执行EnterCriticalSection而不得不处于等待状态。 

再次强调一次,没有任何资源被“锁定”,CRITICAL_SECTION这个东东不是针对于资源的,而是针对于不同线程间的代码段的!我们能够用它来进行所谓资源的“锁定”,

其实是因为我们在任何访问共享资源的地方都加入了EnterCriticalSection和LeaveCriticalSection语句,

使得同一时间只能够有一个线程的代码段访问到该共享资源而已(其它想访问该资源的代码段不得不等待)。 

如果是两个CRITICAL_SECTION,就以此类推。

再举个极端的例子,可以帮助你理解CRITICAL_SECTION这个东东: 
第一个线程函数: 
DWORD   WINAPI   ThreadFuncA(LPVOID   lp) 

            EnterCriticalSection(&cs); 

           
for(int   i=0;i
<1000;i++) 

                       
Sleep(1000); 

           
LeaveCriticalSection(&cs); 

           
return  
0; 

}

第二个线程函数: 
DWORD   WINAPI   ThreadFuncB(LPVOID   lp) 

            EnterCriticalSection(&cs); 

           
index=2; 

           
LeaveCriticalSection(&cs); 

           
return  
0; 



      
这种情况下,第一个线程中间总共Sleep了1000秒钟!它显然没有对任何资源进行什么“有意识”的保护;而第二个线程是要访问资源index的,

      
但是由于第一个线程占用了cs,一直没有Leave,而导致第二个线程不得不登上1000秒钟…… 

第二个线程,真是可怜哪。。。 

这个应该很说明问题了,你会看到第二个线程在1000秒钟之后开始执行index=2这个语句。 

也就是说,CRITICAL_SECTION其实并不理会你关心的具体共享资源,它只按照自己的规律办事~


本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

多线程(C++)临界区Critical Sections问题 的相关文章

  • 这种双重实例是否有害,或者根本没有必要?

    在仔细阅读遗留资源时 我发现了这一点 DataSet myUPC new DataSet myUPC dbconn getDataSet dynSQL Resharper 正确地将其中的 new Dataset 部分 灰显 并建议 删除多余
  • 如何使用带有进度条的 HttpClient 下载文件?

    我创建了一个名为SiteDownload并添加了一些下载图像的链接 using System Collections Generic using System Linq using System Net using System Threa
  • 井字游戏代码有助于改进

    这是我必须检查玩家在井字棋游戏中获胜的代码 这是一个很长的 if 语句 可以改进 该板由 9 个图片框组成 我是一名 C 初学者 pBox Image Player players Player playerTurn getImage ch
  • 如何在 C# 中启动文件

    编辑 我觉得自己像个白痴 我有一种感觉 像下面的答案会起作用 但没有看到任何与下面的答案类似的谷歌结果 所以当我看到这段复杂的代码时 我想它一定是这样的 我搜索并找到了这个Windows 列出并启动与扩展关联的应用程序 https stac
  • 运行 C# exe 文件

    复制 为什么我的 NET 应用程序在从网络驱动器运行时会崩溃 https stackoverflow com questions 148879 why does my net application crash when run from
  • 如何使用 C# 调用 REST API?

    这是我到目前为止的代码 public class Class1 private const string URL https sub domain com objects json api key 123 private const str
  • 如何使用 DesignData 帮助开发 Metro 应用程序?

    我一直在 Windows Phone 应用程序中愉快地使用 DesignData 我希望使用它来帮助在 VS2012 Blend for VS 中的 Metro 风格应用程序中可视化设计 我已经尝试过希望显而易见的方法
  • 有没有办法关闭 Hangfire 使用 Serilog 进行的日志记录?

    有没有办法关闭 Hangfire 使用 Serilog 进行的日志记录 我们正在使用我们自己的抽象 我不希望在使用 Serilog 时来自 Hangfire 记录器的所有额外噪音 INIT call under web project na
  • dlopen 或 dlclose 未调用信号处理程序

    我在随机时间内收到分段错误 我注册了信号 但发生分段错误时未调用信号处理程序 include
  • 二元运算符重载、隐式类型转换

    class my bool private bool value public my bool bool value value value explicit operator bool return value friend my boo
  • 加载配置文件时发生错误:访问路径 c:\Program Files (x86)\... 被拒绝

    我有一个在 Windows 7 上使用 Visual Studio 2010 中的安装程序部署的应用程序 该程序在 Windows 7 和 XP 上部署并运行良好 但当我在 Windows 8 系统上部署它时 出现有关访问配置文件的错误 该
  • 如何设置cookie值?

    我正在执行以下操作来设置 cookie 值 HttpCookie mycookie new HttpCookie mycookie mycookie Value value1 Case sensitivity mycookie Expire
  • C++ fill() 与 uninitialized_fill()

    您好 我是初学者 我想知道容器的 fill 和 uninitialized fill 之间的区别 我在谷歌上进行了快速搜索 但没有得到很好的答案 有人可以帮助我吗 fill 将值 使用赋值运算符 分配给已构造的对象 uninitialize
  • 尝试缓冲区溢出

    我正在尝试使用缓冲区溢出来更改函数的结果 以使用以下代码更改堆栈上的结果 include
  • C语言中的array、&array、&array[0]有什么区别? [复制]

    这个问题在这里已经有答案了 在学习C语言中的数组和指针时 我很困惑 为什么ch ch ch 0 彼此相等 而sptr sptr sptr 0 却不相等 这是我的源代码 int main void char ch 7 1 2 3 4 5 6
  • 如何将此 Boost ASIO 示例应用到我的应用程序中

    我已经阅读了很多 ASIO 示例 但我仍然对如何在我的应用程序中使用它们感到困惑 基本上 我的服务器端需要接受超过100个连接 客户端 这部分是通过使用线程池 通常每个CPU核心2 4个线程 来完成的 为简单起见 我们假设只有一个连接 为了
  • 仅最后一个用户控件显示内容控件

    我有一个奇怪的问题 我创建了一个带有标签和画布的用户控件 画布引用资源 但画布仅显示在我的堆栈面板中的最后一个控件上 这是我的窗户
  • 使用 System.Json 迭代 JSON

    我正在探索 NET 4 5 的功能System Json库 但没有太多文档 而且由于流行的 JSON NET 库 搜索起来相当棘手 我基本上想知道 我如何循环一些 JSON 例如 People Simon Age 25 Steve Age
  • “while(true) { Thread.Sleep }”的原因是什么?

    我有时会遇到以下形式的代码 while true do something Thread Sleep 1000 我想知道这是否被认为是好的做法还是坏的做法以及是否有任何替代方案 通常我在服务的主函数中 找到 这样的代码 我最近在 Windo
  • 如何使用 __m128i 执行元素左移?

    我发现 SSE 移位指令只能在所有元素上移位相同的量 mm sll epi32 mm slli epi32 这些会移动所有元素 但移动量相同 http software intel com sites products documentat

随机推荐

  • 3分钟搞懂:JavaScript 和 ECMAScript

    JavaScript 和 ECMAScript ECMAScript 是 JavaScript 语言的国际标准 JavaScript 是 ECMAScript 的一种实现 Adobe ActionScript 和 JScript 同样实现了
  • 基于Bochs安装GeekOs

    开发环境介绍 1 Ubuntu 16 04 2 boch2 6 11 下载地址 http sourceforge net projects bochs files bochs 2 6 11 3 nasm 2 08 01 下载地址 http
  • CommonJS,ES6 Module以及webpack模块打包原理

    CommonJS ES6 Module以及webpack模块打包原理 模块化历程 CommonJS 模块 导出 导入 ES6 Module 模块 导出 命名导出 默认导出 导入 导入命名导出的模块 导入默认导出的模块 CommonJS 与
  • 夯实C++基础:1.C++生命周期和编程范式、预处理、编译相关

    一直告诉自己要保持学习 但真的工作之后 反而不知道从哪里开始学起 就这么拖着光有想法没有行动 除了加班没有那么晚刷刷题之外 就从看课有人带着学开始夯实基础吧 反正学啥都比不学好 之后可以看设计模式 网络编程 STL深入学一学 也可以看书ef
  • MySQL的安装教程

    MySQL的安装教程 今天来唠一唠MySQL的事 首先是mysql的一些知识点 接下来我们先说MySQL的安装教程 1 安装程序安装 首先 去数据库的官网http www mysql com下载MySQL 一般为 msi文件 下载好之后双击
  • Matlab:从csv文件中读取某一列的数据

    第一种 M CSVREAD FILENAME 直接读取csv文件的数据 并返回给M 第二种 M CSVREAD FILENAME R C 读取csv文件中从第R 1行 第C 1列的数据开始的数据 这对带有头文件说明的csv文件 如示波器等采
  • 华为OD面经(给了口头offer祈祷流程审批能过ε=(´ο`*)))唉)

    1 上来一到算法题相对简单 2 介绍一下自己的项目 问了java的jvm相关如jvm在遇到线程挂掉时的日志操作啥的有做过吗 spring的好处原理 springboot的好处原理 微服务的锁 日志相关 垃圾回收算法 redis的原理 has
  • c#+npgsql采坑记录

    c npgsql 数据库作业采坑记录 做数据库作业时踩了些坑 做个记录 1 pgsql的主键int的模糊查询 pgsql中以int作为主键 比如student以sid作为主键 当sid为int时 模糊查询会使索引失效 而mysql没有这个问
  • Vue的大坑 input手动赋值后无法修改问题

    当获取数据之后 手动赋值给input 会出现渲染成功 能读取数据 但是无法修改情况 代码如下 根据ID查询返回订单信息 async editOrdersAddress orderId const data res await this ht
  • 力学应用计算机实例,PART 5 相图计算机计算 相图计算与 及扩散动力学模拟及其应用实例.ppt...

    PART 5 相图计算机计算 相图计算与 及扩散动力学模拟及其应用实例 ppt 亚点阵模型假设 每一亚点阵内的原子只与其他亚点阵内的原子相邻 这一点可以通过亚点阵的选取来保证 最近邻相互作用是常数 各亚点阵之间的相互作用忽略不计 过剩自由能
  • 前端开发模式的迭代

    前端开发模式的迭代 前端开发给人的印象一直是变化太快 不断出现新的框架 库 开发模式 这些开发模式有什么不同 为什么要不断迭代 本文将分享几种常见的前端开发模式 讲解前端开发模式的演变过程 传统开发模式 前端是 Web 应用的组成部分 前端
  • OpenSSL RSA加密和解密

    rsa加密的密钥格式常见的有两种 一种是PKCS 1 密钥头为 BEGIN RSA PUBLIC KEY 一种是PKCS 8 密钥头为 BEGIN PUBLIC KEY 以字符串公钥为例 对PKCS 1格式的密钥加载使用的函数是PEM re
  • vue自定义指令给指定元素添加水印

    在utils创建一个waterMark js文件 import Vue from vue Vue directive watermark update el binding gt function addWaterMarker parent
  • 基于管道的popen和pclose函数

    标准I O函数库提供了popen函数 它启动另外一个进程去执行一个shell命令行 这里我们称调用popen的进程为父进程 由popen启动的进程称为子进程 popen函数还创建一个管道用于父子进程间通信 子进程要么从管道读信息 要么向管道
  • CSS弹性布局

    弹性布局 弹性布局 也称为flex box 常用于一些自适应网站 可以让网站随浏览器大小变化而变化的网站 从而给用户更好的使用体验 基本属性 1 声明弹性布局 通过display flex 可以让元素声明为flex容器 简称 容器 而它的所
  • android中完全退出当前应用程序的四种方法

    Android程序有很多Activity 比如说主窗口A 调用了子窗口B 如果在B中直接finish 接下里显示的是A 在B中如何关闭整个Android应用程序呢 本人总结了几种比较简单的实现方法 1 Dalvik VM的本地方法 andr
  • Flutter 获取验证码倒计时实现

    Timer countdownTimer String codeCountdownStr 获取验证码 int countdownNum 59 void reGetCountdown setState if countdownTimer nu
  • 机器学习随笔

    1 随机森林在大数据量和feature较多的时候效果比较好 反之的环境下还不如单独的决策树 森林中的每棵树都是独立的 99 9 不相关的树做出的预测结果涵盖所有的情况 这些预测结果将会彼此抵消 少数优秀的树的预测结果将会超脱于芸芸 噪音 做
  • 【Spring

    Resource Resource 接口 介绍 核心方法 常见接口 优缺点 内置 Resource 实现 UrlResource ClassPathResource FileSystemResource PathResource Servl
  • 多线程(C++)临界区Critical Sections问题

    多线程中用来确保同一时刻只有一个线程操作被保护的数据 InitializeCriticalSection cs 初始化临界区 EnterCriticalSection cs 进入临界区 操作数据 MyMoney 10 所有访问MyMoney