欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

互斥那点事儿(上)

程序员文章站 2022-10-04 13:21:35
本年度第 10 次操作系统成员会议开始啦! 一月一度的会议旨在让大家互相交流,解决最近在工作中出现的问题,以提高整个计算机系统的工作效率。因为计算机硬件在飞速发展, 而操作系统是连接计算机硬件和应用程序的中间层 ,如果故步自封,很快就会被市场淘汰,所以每位操作系统成员都很重视月度会议。 这次提出问题 ......

本年度第 10 次操作系统成员会议开始啦!

一月一度的会议旨在让大家互相交流,解决最近在工作中出现的问题,以提高整个计算机系统的工作效率。因为计算机硬件在飞速发展,而操作系统是连接计算机硬件和应用程序的中间层,如果故步自封,很快就会被市场淘汰,所以每位操作系统成员都很重视月度会议。

这次提出问题的是进程和线程两兄弟。

站在众人前面,线程显得有些怯场,他戳了戳进程,示意让他先来讲。进程迅速整理了下思路,挺直了身板,说:“这次的问题是在一个订票系统里发现的,我把这个系统的简单逻辑画出来了,你们一边看我一边说。”

互斥那点事儿(上)

“这个订票系统分为服务器端(server)和客户端(client),当用户与服务器建立连接时,服务器端就会建立一个新的线程来为客户端提供服务。订票逻辑是这样的:

互斥那点事儿(上)

单独从这个逻辑图上看是没有问题的,但在实际情况下,因为经常出现多个用户同时抢订一张票的情景,这种方式就可能会出错。就像这样:

互斥那点事儿(上)

在线程 a 确定完余票(假设是 1),但还未能成功订票之前,线程 b 得到了余票数为 1 的信息,所以 b 也认为可以订票,最后导致一张票卖出去两份。“

内存一针见血的道:“我看这就是几个线程执行流的冲突问题嘛,本来应该一个线程订票操作结束后,另一个线程才能查询余票。像这样执行流交叉,肯定还会出现其它意想不到的问题。”

进程佩服的说:“诶别说,内存你说的太有道理了,我也遇到过类似的情况,上次我和另一个进程共享一部分内存空间,结果在使用同一个数据的时候,他把我刚写进去的数据覆盖掉了,害得我后面的计算全出错了。”

这时,磁盘发表了他的看法:“执行流的问题,那一看就是进程调度器的锅,怎么非得在别人执行到关键步骤的时候把人家从 cpu 上赶下来!要是调度器稍微等一会儿,这问题不就解决了?”

进程调度器听到这话,气的站起来,说:“你,你怎么凭空污人清白!什么时候切换进程不是由我来决定好不好?我是负责从就绪队列选出最应该使用 cpu 的进程而已。等我开始调度的时候,那些进程就已经被操作系统撤下来了。”

操作系统补充道:“调度器说的没错,调度的时机是由中断决定的。看样子这种情况出现在进程时间片用尽的时候,出现了时钟中断,然后被其他进程抢占了 cpu 资源。”

磁盘听了,不好意思的说:“对不起,刚刚是我太武断了。那照你的意思,我们在执行到这部分代码的时候,像这样屏蔽时钟中断可以解决这个问题了?”

互斥那点事儿(上)

操作系统摇摇头:“「中断禁用」这种方式确实可以防止进程在运行这部分代码时进行切换,但是,时钟中断是我的一项非常重要的功能,怎么能随随便便就把控制权交给人类呢?万一有的程序员想要他们的代码可以完全占有 cpu ,不把时钟中断给我开启怎么办?我是不可能把这种重要权限交出去的,我要对整个系统负责。”

内存在旁边赞同道:“除了这一方面,你还要知道,现在都是多核时代了,你即使禁用了这个 cpu 的时钟中断,其他几个核还是能切换进程,然后访问这些数据。磁盘啊,你明明存了那么多文件,怎么懂得还是那么少。。。”

磁盘愤愤的道:“别瞧不起我,我这就去找有没有办法解决这个问题!”

思考了许久的 cpu 开口了:“我来捋一捋吧,现在咱的目标是,不让两个进程同时执行这一段代码——我们把这段代码叫做临界区吧,换句话说,我们需要让进程互斥的进入临界区。那我们就把这段临界区「加锁」,”

“加锁?这是什么意思?”

“加锁是个比喻,其实「」只是一个共享变量,我们可以让它有 openclose 这两个值。一个进程,比如说 a,进入临界区之前,先检查锁是不是 open 状态,如果是的话,就把锁改为 close 状态 ,这样其他进程在进入临界区时,会发现锁已经 close 了,那就让他们循环等待 ,直到 a 出临界区然后将锁打开。”

内存眉头一皱,发现事情并没有这么简单——如果 a 发现锁是开着的,但在 a 还没有关闭锁之前,切换到了进程 b ,那么 b 也会发现锁是开着的,那么 b 也将能够进入临界区

想到这里,内存把问题告诉 cpu,但 cpu 说,这对他不是问题。

原来计算机里有一条硬件支持的指令——tsl(test and set lock,测试并加锁),这条指令可以保证读字和写字的操作「不可分割」,也就是说,在这条指令结束前,就连其他处理器也不可能访问该内存字。

“tsl 指令会把内存字 lock 读到寄存器上,然后在对应的内存地址上写入一个非零值。那我们就可以利用这条指令改进刚刚的加锁的方法,就像这样:

互斥那点事儿(上)

我们让进程在进入临界区之前先调用 enter_region ,如果锁已经被关闭(表现为锁非 0 ),就循环调用enter_region ,直到锁打开,然后再进入临界区。出临界区之后,就调用 leave_region 把锁打开。这样不就解决你的问题了?“

内存点点头,说:“这确实是一个好方法,解决了临界区的互斥问题。”

不过操作系统不是很满意这种解决方案:“这种解决方式需要忙等待,浪费了 cpu 的资源啊,我觉得这种 tsl 方案需要改进。”

这时候大家陷入了沉默——谁也没有想到更好的解决方案,会议好像就此僵住了。

谁能想到一种更好的方案呢?


哈哈,我在文章里埋了伏笔哦,你猜猜是谁找到了更好的方法呢?

觉得我写的还不错的话,就点个赞吧!

声明:原创文章,未经授权,禁止转载