C语言 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,就以此类推。

再举个极端的例子,可以帮助你理解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其实并不理会你关心的具体共享资源,它只按照自己的规律办事~

到此这篇关于C语言 CRITICAL_SECTION用法案例详解的文章就介绍到这了,更多相关C语言 CRITICAL_SECTION用法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深度理解C语言中的关键字static

    目录 一.函数和变量的多文件问题 1.1.为什么全局变量和函数需要跨文件访问 二.static修饰变量和函数 2.1.static修饰全局变量 2.2.static修饰局部变量 2.3.为什么局部变量具有临时性,全局变量具有全局性 总结 一.函数和变量的多文件问题 .h: 头文件,一般包含函数声明,变量声明,宏定义,头文件等内容(header) .c : 源文件,一般包含函数实现,变量定义等 (.c:c语言) 如果在一个源文件定义一个函数,然后再另一个源文件调用,这样的方式可行吗? 答案是可行的

  • C语言JNI的动态注册详解

    目录 总结 JNI的静态注册就是Javah生成头文件,本章第一篇已经讲过,现在我们来讲讲第二种方式,JNI动态注册.首先是module的build.gradle: android { compileSdkVersion 30 buildToolsVersion "30.0.3" defaultConfig { applicationId "com.jhzl.a7_jni_2way" minSdkVersion 21 targetSdkVersion 30 versi

  • C语言操作符基础知识图文详解

    目录 1.算术操作符 + - * / % 2.移位操作符 >> (右移操作符) <<(左移操作符) 左移操作符:<< 右移操作符:>> 3.位操作符 3.1 & 按位与 3.2 | 按位或 3.3 ^ 按位异或 3.4 ~ 按位取反 4.赋值操作符 = 5.逻辑操作符 5.1 ! 非 5.2 && 与 5.3 || 或 6.条件运算符 ? 7.单目操作符 7.1 ! 7.2 + 正值 7.3 - 负值 7.4 & 取地址 7.

  • C语言动态内存管理的实现

    目录 1. 摘要 2. 为什么存在动态内存管理 3. 动态内存函数 3.1 malloc 3.2 free 3.3 calloc 3.4 realloc 4. 常见的动态内存错误 5. 几个经典笔试题 参考答案 6. 参考文献 1. 摘要 本文主要详解C语言中的动态内存分配 2. 为什么存在动态内存管理 我们先来看一段变量的声明: double x = 1.000000; char str[] = "abcdef"; 好的,上述变量的声明有何特点呢? 请思考一下,我的朋友. 对,没错,

  • C语言 TerminateProcess函数案例详解

    TerminateProcess 顾名思义,就是终止进程的意思. 是WindowsAPI的函数, 示例代码如下: // Demo.cpp : 定义控制台应用程序的入口点. //终止进程Demo #include "stdafx.h" using namespace std; //@param:dwpid:指定需要关闭的进程pid int CloseProcess(DWORD dwpid) { HANDLE hProcess = OpenProcess(PROCESS_TERMINATE

  • C语言 bind()函数案例详解

    bind()函数介绍        在建立套接字文件描述符成功后,需要对套接字进行地址和端口的绑定,才能进行数据的接收和发送操作. 函数原型        bind()函数将长度为addlen的struct sockadd类型的参数my_addr与sockfd绑定在一起,将sockfd绑定到某个端口上,如果使用connect()函数则没有绑定的必要.绑定的函数原型如下: #include<sys/types.h> #include<sys/socket.h> int bind(in

  • C语言 module_init函数与initcall案例详解

    module_init这个函数对做驱动的人来说肯定很熟悉,这篇文章用来跟一下这个函数的实现. 在include/linux/init.h里面有module_init的定义,自然,因为一个module可以在内核启动时自动加载进内核,也可以由我们手动在需要时加载进内核,基于这种场景,内核使用了MODULE这个宏,见代码: #ifndef MODULE #ifndef __ASSEMBLY__ ... #define __define_initcall(level,fn,id) \ static in

  • C语言中栈的两种实现方法详解

    目录 一.顺序栈 二.链式栈 总结 一.顺序栈 #include<stdio.h> #include<stdlib.h> #define maxsize 64 //定义栈 typedef struct { int data[maxsize]; int top; }sqstack,*sqslink; //设置栈空 void Clearstack(sqslink s) { s->top=-1; } //判断栈空 int Emptystack(sqslink s) { if (s-

  • C语言 CRITICAL_SECTION用法案例详解

          很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION是锁定了资源,其实,CRITICAL_SECTION是不能够"锁定"资源的,它能够完成的功能,是同步不同线程的代码段.简单说,当一个线程执行了EnterCritialSection之后,cs里面的信息便被修改,以指明哪一个线程占用了它.而此时,并没有任何资源被"锁定".不管什么资源,其它线程都还是可以访问的(当然,执行的结果可能是错误的).只不过,在这个线程尚未执

  • CRITICAL_SECTION用法案例详解

          很多人对CRITICAL_SECTION的理解是错误的,认为CRITICAL_SECTION是锁定了资源,其实,CRITICAL_SECTION是不能够"锁定"资源的,它能够完成的功能,是同步不同线程的代码段.简单说,当一个线程执行了EnterCritialSection之后,cs里面的信息便被修改,以指明哪一个线程占用了它.而此时,并没有任何资源被"锁定".不管什么资源,其它线程都还是可以访问的(当然,执行的结果可能是错误的).只不过,在这个线程尚未执

  • C语言 联合(union)用法案例详解

    联合(union)的声明和结构与结构体类似,但是本质不同.    联合的所有成员引用的是内存中的相同位置.当你想在不同时刻把不同的东西存储于同一位置时,就可以使用联合.   构体(struct)中所有变量是"共存"的--优点是"有容乃大",全面:缺点是struct内存空间的分配是粗放的,不管用不用,全分配.   而联合体(union)中是各变量是"互斥"的--缺点就是不够"包容":但优点是内存使用更为精细灵活,也节省了内存空间

  • C语言指针与引用的区别以及引用的三种用法案例详解

    1.指针与引用的区别: 指针是一块内存的地址值,而引用是一块内存的别名. 下面引自://www.jb51.net/article/221791.htm 从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量). 在C++中,指针和引用经

  • C语言strtod()函数案例详解

    前言 网上有很多关于strtod()函数的文章,不过大部分都是用strtod()函数转换一个字符 char *str = "111.11"; char *target; double ret; ret = strtod(str, &target); 很少有转换字符串的这样的用法 char *p = "111.11 -2.22 Nan nan(2) inF 0X1.BC70A3D70A3D7P+6 1.18973e+4932zzz"; 本文主要参考strtod

  • java之assert关键字用法案例详解

    Java2在1.4中新增了一个关键字:assert.在程序开发过程中使用它创建一个断言(assertion).,它的语法形式有如下所示的两种形式: 1.assert condition; 这里condition是一个必须为真(true)的表达式.如果表达式的结果为true,那么断言为真,并且无任何行动 如果表达式为false,则断言失败,则会抛出一个AssertionError对象.这个AssertionError继承于Error对象, 而Error继承于Throwable,Error是和Exc

  • C++ assert()函数用法案例详解

    1. 简介 assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行. 原型定义: #include <assert.h> void assert( int expression ); assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行.请看下面的程序清单badptr.c: #include <stdio.h> #incl

  • C语言指针数组案例详解

    指针与数组是 C 语言中很重要的两个概念,它们之间有着密切的关系,利用这种 关系,可以增强处理数组的灵活性,加快运行速度,本文着重讨论指针与数组之 间的联系及在编程中的应用. 1.指针与数组的关系 当一个指针变量被初始化成数组名时,就说该指针变量指向了数组.如: char str[20], *ptr; ptr=str; ptr 被置为数组 str 的第一个元素的地址,因为数组名就是该数组的首地址, 也是数组第一个元素的地址.此时可以认为指针 ptr 就是数组 str(反之不成立), 这样原来对数

  • Python中return用法案例详解

    python中return的用法 1.return语句就是把执行结果返回到调用的地方,并把程序的控制权一起返回 程序运行到所遇到的第一个return即返回(退出def块),不会再运行第二个return. 例如: def haha(x,y): if x==y: return x,y print(haha(1,1)) 已改正: 结果:这种return传参会返回元组(1, 1) 2.但是也并不意味着一个函数体中只能有一个return 语句,例如: def test_return(x): if x >

  • Java ConcurrentHashMap用法案例详解

    一.概念 哈希算法(hash algorithm):是一种将任意内容的输入转换成相同长度输出的加密方式,其输出被称为哈希值. 哈希表(hash table):根据设定的哈希函数H(key)和处理冲突方法将一组关键字映象到一个有限的地址区间上,并以关键字在地址区间中的象作为记录在表中的存储位置,这种表称为哈希表或散列,所得存储位置称为哈希地址或散列地址. 二.HashMap与HashTable 1,线程不安全的HashMap 因为多线程环境下,使用HashMap进行put操作会引起死循环,导致CP

随机推荐