一文带你彻底理解同步和锁的本质(干货)(转载)

一文带你彻底理解同步和锁的本质(干货)

谈到锁,离不开多线程,或者进程间的通信。 为了更好地从底层原理去了解锁的机制,形成体系化的知识,这篇文章我会从进程间通信底层原理说起,然后介绍一下Java中各种线程通信的实现机制,最后做一个系统的总结。

还记得上次跟你撕逼内存模型的那个人吗,他又来了,并且向你甩出了一堆问题:

image-20200225000533712

 

image-20200225000543895

 

image-20200225000553812

 

1、为什么需要通信

1.1、竞态条件

我们知道,在操作系统中,互相协作的进程之间可能共享一些彼此都能读写的公共存储区,假设两个进程都需要改写这个公共的存储区那么就会产生竞争关系了。

下面举个例子

假设两个进程a和b共享一个脱机目录,脱机目录中有许多槽位,free记录了下一个空的槽位,进程可以往下一个空槽位中写入内容。

进程a准备往下一个空槽位写入内容"test",进程b准备往下一个空槽位写入内容“good”。

我们来分析下极端情况:

image-20200222215815129

 

可以发现,由于发生了时钟中断,两个进程都往槽位3写入了内容,进程b的内容被进程a的内容覆盖掉了。

像这种由于两个或者多个进程读写某些共享数据,最后结果取决于进程运行的精确时序,称为竞态条件

为了避免这种竞态条件的出现,就需要找出存在这种竞态条件的程序片段,通过互斥的手段来阻止多个进程同时读写共享的数据。

1.2、临界区

对共享内存进行访问的程序片段称为临界区

为了实现互斥而选择适当的原语是任何操作系统的主要涉及内容之一。 后面我们会详细讨论各种实现互斥的手段,这些手段也是实现进程通信或者线程通信的技术基础。

还是以上面的例子来说明,为了避免竞态条件的产生,我们需要把获取空槽位和往槽位写内容的程序片段作为一个临界区,任何不同的进程,不可以在同一个时刻进入这个临界区:

image-20200223095433203

 

如上图,进程b试图在a离开临界区之前进入临界区,会进入不了,导致阻塞,通常表现的行为为: 进程挂起或者自旋等待。

为了实现这种临界区的互斥,需要进程之间能够像对话一样,确认是否可以进入临界区执行代码,这种对话即进程通信。 有很多经典的处理方法,下面我们就逐个的来介绍。

2、常见的实现进程通信的手段

2.1、忙等待的互斥(自旋等待)

所谓忙等待,指的是进程自己一直在循环判断是否可以获取到锁了,这种循环也称为自旋。 下面我们通过屏蔽中断锁变量的介绍,依次引出忙等待的相关互斥手段方法。

2.1.1、屏蔽中断

如下图,在进程进入临界区之前,调用local_irq_disable宏来屏蔽中断,在进程离开临界区之后,调用local_irq_disable宏来使能中断。

image-20200223095140312

 

CPU只有发生时钟中断或其他中断才会进行进程切换,也就是说,屏蔽中断后,CPU不会切换到其他进程。但是,这仅仅对执行disable的那个CPU有效,其他CPU仍将继续运行,也就是说多核处理器这种手段无效。

另外,这个屏蔽中断是用户进程触发的,如果用户进程长时间没有离开临界区,那就意味着中断一直启用不了,最终导致整个系统的终止。

由此可见,在这个多核CPU普及的时代,屏蔽中断并不是实现互斥的良好手段。

2.1.2、锁变量

上面一种硬件的解决方案,既然硬件解决不了,那么我们尝试通过软件层面的解决方案去实现。 我们添加一个共享锁变量,变量为0,则表示可进入临界区,进入之后,设置为1,离开临界区重置为0,如下图所示:

image-20200223100855287

 

但是由于对Lock的check和set是分为两步,并非原子性的,那么可能会出现如下情况:

image-20200223101930928

 

也就是说在进程a把Lock设置为1之前,b就进行check和set操作了,也获取到了Lock=0,导致两个进程同时进入了临界区。

这种非原子性的检查并设置锁操作还是会存在竞态条件,并不能作为互斥的解决方案。

接下来我们升级一下程序,为了避免这种竞态条件,我们让进程间严格轮换的方式去争抢使用Lock的机会。

2.1.3、严格轮换法

所谓严格轮换法,就是指定一个标识位turn,当turn=0的时候让进程a进入临界区,当turn=1的时候,让进程b进入临界区。 以下是实现代码:

 1// 进程a 2while(TRUE){ 3    while(turn != 0);      /* 循环测试turn,看其值何时变为0 */ 4    critical_region();     /* 进入临界区 */ 5    turn = 1;              /* 让给下一个进程处理 */ 6    noncritical_region();  /* 离开临界区 */ 7} 8// 进程b 9while(TRUE){10    while(turn != 1);      /* 循环测试turn,看其值何时变为1 */11    critical_region();     /* 进入临界区 */12    turn = 0;              /* 让给下一个进程处理 */13    noncritical_region();  /* 离开临界区 */14}复制代码

这种方法可能导致在循环中不停的测试turn,这称为忙等待,比较浪费CPU,只有有理由认为等待时间是非常短的情形下,才使用忙等待,用于忙等待的锁,称为自旋锁(spin lock)。

假设现在进程a在临界区里面,并且执行了turn=1,准备把临界区轮换给进程b,但是这个时候进程b正在处理其他事情,那么这个临界区就一直被进程b阻塞了。 进程a想重新进入也需要等待。

也就是说,我唱完一首歌,把麦给了你,轮到你唱,这个时候你拿着麦去上厕所了。 那么我想唱歌,也只能等到你上完厕所,唱完歌,把麦的使用权交接给我,我才可以继续唱。

你上厕所竟然影响到了我唱歌,就是所谓的临界区外运行的进程阻塞了其他想进入临界区的进程。

看来这种解决方案并不是一个很好的

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
操作系统是计算机系统中的核心组成部分,负责管理和协调计算机硬件和软件资源,提供程序运行环境。在CSDN上有很多关于操作系统的专题文章,以下将从操作系统的基本概念、功能和常见类型等方面简要介绍一下。 首先是操作系统的基本概念。操作系统是一种系统软件,它是计算机硬件和应用软件之间的桥梁,提供给应用程序一系列的服务和资源,同时负责调度和管理系统资源。它为用户屏蔽了底层的硬件差异,提供了一个统一的、易于使用的界面。 操作系统主要有四个基本功能。首先是处理器管理,负责将处理器分配给系统中的各个进程,并进行进程切换,实现多道程序并发执行。其次是内存管理,管理计算机的内存资源,包括分配、回收和保护等操作。再次是文件管理,负责管理文件的存储、命名和保护等操作,提供了文件操作的接口。最后是设备管理,负责管理计算机的各种设备,包括输入输出设备和存储设备等。 常见的操作系统有多种类型。最主流的是Windows、Linux和Mac OS等桌面操作系统。此外还有服务器操作系统,如Windows Server和Linux等,用于管理和部署服务器。还有嵌入式操作系统,如Android和iOS等,用于移动设备和物联网设备。操作系统也有实时操作系统,用于需要实时控制和响应的系统,如工控系统和航空航天系统等。 总之,操作系统是计算机系统中不可或缺的重要组成部分,通过CSDN上的相关文章,我们可以更深入了解操作系统的基本概念、功能和不同类型。这些知识对于理解计算机系统的工作原理和提升编程能力都有着重要意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值