1.线程安全问题:

每个线程都有自己的栈,而局部变量是储存在栈中的,这就意味着每个线程都有一份自己的“局部变量”,如果线程仅仅使用“局部变量”那么就不存在线程安全问题。

那如果多个线程共用一个全局变量呢?

多线程不一定存在线程安全问题,多线程访问全局变量也不一定有安全问题,多线程如果对全局变量只读那也没有安全问题。

那么多线程的线程安全问题的前提是:

<1>有全局变量

<2>对全局变量不是只读,有写的动作。

我写一段代码让两个线程对全局变量同时有写的动作

#include"stdafx.h"
#include<stdio.h>
int Tickets = 10;
DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter)
{while (Tickets > 0)         //判断是否还有余票{printf("还有:%d张票\n", Tickets);Tickets--;              //这里就是写了            printf("卖出一张,还有:%d张\n", Tickets);}return 0;
}
int main(int argc, char argv[])
{DWORD result1;DWORD result2;HANDLE hThread[2];hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);//创建线程hThread[1] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);//创建线程WaitForMultipleObjects(2, hThread, TRUE, INFINITE);//等待两个线程执行完毕GetExitCodeThread(hThread[0], &result1);GetExitCodeThread(hThread[1], &result2);printf("\n%d %d\n", result1, result2);getchar();return 0;
}

输出

注意:每次输出的结果可能是不一样的

如图这就是所谓的线程安全问题。那么为什么会发生这种情况呢?

首先我们知道这两个线程是自己跑自己的互不影响,不过我们知道两个线程同时进行是通过交替来实现的,也就是说这两个线程交替访问这个全局变量,在任何一串代码执行完后都有可能切换到另一个线程。这样说可能不太清晰我们来假设一下。

假设还剩2张票吧

DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter) //X线程
{while (Tickets > 0)         //判断是否还有余票{//Aprintf("还有:%d张票\n", Tickets);Tickets--;printf("卖出一张,还有:%d张\n", Tickets);}return 0;
}
​
​Tickets =10
​
​
​
DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter)//Y线程
{while (Tickets > 0)         //判断是否还有余票{printf("还有:%d张票\n", Tickets);Tickets--;printf("卖出一张,还有:%d张\n", Tickets);}return 0;
}

如上图所示这样就会出现

这样的情况(注意:线程X切换到Y在切换回来会在切换前的地方A继续运行)

2.解决思路

我们出现这种情况的原因不就是因为两个线程同时读写了全局变量吗,那么我们是不是可以同时(从开始到结束)只让一个线程访问呢?这样可不可以解决问题呢?

我们先要理解一个概念叫做临界资源和临界区

临界资源是一次仅允许一个线程使用的资源
访问临界资源的那段程序称为临界区

画图方便理解 意思就是当令牌被一个线程拿到时必须归还令牌另一个线程才能使用

那么我们想要安全访问的话就要构建临界区,构建临界区我们可以自己写代码也可以使用给的API来构建。

自己写代码太难了(我不会)。我们就使用给的API来构建吧。

3.临界区实现之线程锁:

<1>创建全局变量:

CRITICAL_SECTION cs;

我们把这个结构体创建为全局变量,就创建叫cs吧(随便起的)

<2>初始化全局变量:

InitializeCriticalSection(&cs);

使用前初始化

<3>实现临界区:

1.EnterCriticalSection(&cs);//临界区开始的地方

void EnterCriticalSection([in, out] LPCRITICAL_SECTION lpCriticalSection//指向关键部分对象的指针。(cs)
);

2. 使用临界资源(就是上下两个函数夹着的地方)

3.LeaveCriticalSection(&cs);//离开临界区的地方

void LeaveCriticalSection([in, out] LPCRITICAL_SECTION lpCriticalSection//指向关键部分对象的指针。(cs)
);

ok我们现在来试试临界区

#include"stdafx.h"
#include<stdio.h>
int Tickets = 10;
CRITICAL_SECTION cs;
DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter)
{while (Tickets > 0)         //判断是否还有余票{EnterCriticalSection(&cs);//进入临界区printf("还有:%d张票\n", Tickets);Tickets--;printf("卖出一张,还有:%d张\n", Tickets);LeaveCriticalSection(&cs);//离开临界区}return 0;
}
int main(int argc, char argv[])
{InitializeCriticalSection(&cs);DWORD result1;DWORD result2;HANDLE hThread[2];hThread[0] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);//创建线程hThread[1] = CreateThread(NULL, 0, ThreadProc1, NULL, 0, NULL);//创建线程WaitForMultipleObjects(2, hThread, TRUE, INFINITE);//等待两个线程执行完毕GetExitCodeThread(hThread[0], &result1);GetExitCodeThread(hThread[1], &result2);printf("\n%d %d\n", result1, result2);getchar();return 0;
}

这时当线程X在访问全局变量时Y肯定是不行的,不过这样就绝对安全了吗。我们看看输出

这是为什么呢。原来我们没把对全局变量的判断也放入临界区。就比如当X进入临界区还剩一张票,这时Y虽然进不了临界区但是这时Y判断也是还有一张票这时Y就通过判断等着X通过临界区了。好了X通过临界区票剩0张Y又进入临界区票就变成-1张了。所以我们在创建临界区的时候应该把对全局变量的判断,访问等各种操作全部放入临界区中。

我们修改一下创建的线程

DWORD WINAPI ThreadProc1(_In_ LPVOID lpParameter)
{EnterCriticalSection(&cs);//放到这里while (Tickets > 0)         //判断是否还有余票{printf("还有:%d张票\n", Tickets);Tickets--;printf("卖出一张,还有:%d张\n", Tickets);}LeaveCriticalSection(&cs);//放到这里return 0;
}

其他地方不用修改我就不写了

看一下输出没问题了。

Win32学习笔记(10)临界区相关推荐

  1. thinkphp学习笔记10—看不懂的路由规则

    原文:thinkphp学习笔记10-看不懂的路由规则 路由这部分貌似在实际工作中没有怎么设计过,只是在用默认的设置,在手册里面看到部分,艰涩难懂. 1.路由定义 要使用路由功能需要支持PATH_INF ...

  2. SpringMVC:学习笔记(10)——整合Ckeditor且实现图片上传

    SpringMVC:学习笔记(10)--整合Ckeditor且实现图片上传 配置CKEDITOR 精简文件 解压之后可以看到ckeditor/lang下面有很多语言的js,如果不需要那么多种语言的,可 ...

  3. springmvc学习笔记(10)-springmvc注解开发之商品改动功能

    springmvc学习笔记(10)-springmvc注解开发之商品改动功能 springmvc学习笔记(10)-springmvc注解开发之商品改动功能 标签: springmvc springmv ...

  4. Hadoop学习笔记—10.Shuffle过程那点事儿

    Hadoop学习笔记-10.Shuffle过程那点事儿 一.回顾Reduce阶段三大步骤 在第四篇博文<初识MapReduce>中,我们认识了MapReduce的八大步骤,其中在Reduc ...

  5. Linux学习笔记10

    Linux学习笔记10 Linux学习笔记10 正则表达式 源码包约定目录 Shell脚本约定目录 Shell脚本的创建与执行 date命令 同步时间 Shell脚本预设变量 与用户交互 数学计算 S ...

  6. HALCON 20.11:深度学习笔记(10)---分类

    HALCON 20.11:深度学习笔记(10)---分类 HALCON 20.11.0.0中,实现了深度学习方法. 本章解释了如何在训练和推理阶段使用基于深度学习的分类. 基于深度学习的分类是一种对一 ...

  7. 台大李宏毅Machine Learning 2017Fall学习笔记 (10)Tips for Deep Learning

    台大李宏毅Machine Learning 2017Fall学习笔记 (10)Tips for Deep Learning 注:本博客主要参照 http://blog.csdn.net/xzy_thu ...

  8. Python学习笔记--10.Django框架快速入门之后台管理admin(书籍管理系统)

    Python学习笔记--10.Django框架快速入门之后台管理 一.Django框架介绍 二.创建第一个Django项目 三.应用的创建和使用 四.项目的数据库模型 ORM对象关系映射 sqlite ...

  9. 史上最牛最强的linux学习笔记 10.shell基础

    史上最牛最强的linux学习笔记 10.shell基础 写在最前面: 本文是基于某站的视频学习所得,第一个链接如下: https://www.bilibili.com/video/BV1mW411i7 ...

最新文章

  1. 【转知乎】人工智能会是泡沫吗?
  2. 产品经理必须要了解的经济学原理--“口红效应”
  3. 汇编语言中可以定义变量吗?怎么定义?有局部变量和全局变量之分吗?作用域是什么?
  4. 漂亮图片演示ajax制作教程-lightbox
  5. VC++ 6.0如何创建与调用动态链接库
  6. CSS:结合clip-path实现目录的隐藏显示以及提示框的隐藏显示
  7. LMM(LightMoonMovie)亮月湾电影分享管理系统;
  8. java发送邮件带附件
  9. 数据库系统及应用——班级管理系统
  10. 计算机四级 网络工程师 考过指南
  11. ★★★HEU_KMS_Activator_v7.5 (附详细说明文档)
  12. Cisco 思科模拟器命令
  13. 一步步学习k8s(二)
  14. 热门招聘丨 XTransfer史上最全产品技术岗位公开招聘
  15. 银行案例分析:识别个人贷款客户画像,实现精准营销与风险防范
  16. java服务器端集成微信小程序
  17. SNAT和DNAT原理及配置方法
  18. NR 物理层 卷积 狄拉克函数八讲1-狄拉克函数定义Delta Function
  19. 建立时间和保持时间的计算
  20. 1.8寸TFT LCD128X160 ST7735S SPI串口屏驱动示例

热门文章

  1. 地铁当月打折后总费用计算公式分享
  2. kubernetes系列五之service管理
  3. Python中怎么清屏
  4. RestEasy简介
  5. 如何理解「可怕的是比你优秀的人比你更努力」这句话?
  6. Unity创建布娃娃ragdoll源码以及布娃娃的坑
  7. SQL 用户管理和授权
  8. nasm纠正性训练指南pdf_力量训练前做这套静态拉伸,可以大大减轻健身后的肌肉酸痛...
  9. 聚合支付,实现微信和支付宝扫一码支付
  10. 虚拟服务器销售犯罪吗,卖云服务器犯法吗