操作系统

同步、异步、的概念

同步:当一个同步调用发出后,调用者要一直等待返回结果。通知后,才能进行后续的执行。

异步:当一个异步过程调用发出后,调用者不能立刻得到返回结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

进程

3.1 为啥要引入进程的概念?

程序的封闭性和可再现性。

程序的并发执行和资源共享

总的来说就是,多道程序设计的环境下,程序的并发执行代替了程序的顺序执行

程序并发执行的特性

增加了系统的处理能力,

<1>提高了系统的资源利用率但是失去了程序的封闭性,

<2>程序和机器的执行顺序活动不再是一一对应

进程概念的引入

程序的并发执行代替了程序的顺序执行它破坏了程序的封闭性和可再现性,使得程序和计算不再一一对应

进程的定义:进程是程序的一次执行,该程序可以和其他的程序并发执行(进程是系统进行资源分配和调度的一个独立单位,是系统中的并发执行的单位。)

  • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
  • 并发性:任何进程都可以同其他进程一起并发执行
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
  • 结构特征:进程由程序、数据和进程控制块三部分组成;
  • 多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果; 但是执行过程中,程序不能发生改变。

3.1.2 进程的组成

程序、数据集合、进程控制块(PCB)

进程调度的状态

运行状态:进程已经获得必要的资源,并且占有一个处理机,处理机正在执行该进程的程序。

就绪状态:如果进程已经具备了运行的条件,但是由于处理机已经被其他的进程占用,因此暂时不能运行,而是等待分配处理机。

阻塞状态:进程在运行的过程中,因为等待某一个事件(如等待某一个输入输出操作的完成而不能运行的状态,称之为阻塞状态,即进程的运行受到了阻塞)

正在处理机上运行的程序称为该处理机的现运行进程。在任何的时候,一个系统的现运行的进程数一定少于等于可用的处理机的数目。

线程及其管理

3.2.1 线程概念的引入

操作系统中引入进程的目的在于:提高系统的效率,提高系统的资源利用率。

进程因创建而产生,经调度程序的调度而运行,因等待某一时间而阻塞,最后因任务完成而撤销。也就是说,在进程的整个生存期内,不断地改变它的运行环境。在进程调度的过程中,进程的切换更是常有。在进程进行切换的时候,既要保存现进程的运行环境,又要设置所选中的运行环境。为此要花费不小的处理机的时间。因此将进程作为系统调度的基本单位要付出较大的时空开销,从而限制了系统中进程的数量以及进程切换的频率。从而引进了线程的概念,线程是系统调度的基本单位,但不是独立分配资源的基本单位,使之轻装运行,而对于拥有了资源的基本单位又不频繁的对其进行切换,引进了线程的概念后,既减少了时空的开销,又增强了系统的并行能力。

线程是进程的一个实体,也是 CPU 调度和分派的基本单位,它是比进程更小的能独立运行的基本单位,有时又被称为轻权进程或轻量级进程。

3.2.2 线程和进程的区别

进程是资源分配的最小单位,而线程是 CPU 调度的最小单位;

创建进程或撤销进程,系统都要为之分配或回收资源,操作系统开销远大于创建或撤销线程时的开销;

不同进程地址空间相互独立,同一进程内的线程共享同一地址空间。一个进程的线程在另一个进程内是不可见的;

进程间不会相互影响,而一个线程挂掉将可能导致整个进程挂掉;

协程

为什么需要协程?

我们都知道多线程,当需要同时执行多项任务的时候,就会采用多线程并发执行。拿手机支付举例子,当收到付款信息的时候,需要查询数据库来判断余额是否充足,然后再进行付款。

假设最开始我们只有可怜的10个用户,收到10条付款消息之后,我们开启启动10个线程去查询数据库,由于用户量很少,结果马上就返回了。第2天用户增加到了100人,你选择增加100个线程去查询数据库,等到第三天,你们加大了优惠力度,这时候有1000人同时在线付款,你按照之前的方法,继续采用1000个线程去查询数据库,并且隐隐觉察到有什么不对。

几天之后,见势头大好,运营部门开始不停的补贴消费券,展开了史无前例的大促销,你们的用户开始爆炸增长,这时候有10000人同时在线付款,你打算启动10000个线程来处理任务。等等,问题来了,因为每个线程至少会占用4M的内存空间,10000个线程会消耗39G的内存,而服务器的内存配置只有区区8G,这时候你有2种选择,一是选择增加服务器,二是选择提高代码效率。那么是否有方法能够提高效率呢?

我们知道操作系统在线程等待IO的时候,会阻塞当前线程,切换到其它线程,这样在当前线程等待IO的过程中,其它线程可以继续执行。当系统线程较少的时候没有什么问题,但是当线程数量非常多的时候,却产生了问题。一是系统线程会占用非常多的内存空间,二是过多的线程切换会占用大量的系统时间。

协程刚好可以解决上述2个问题。协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多。

回到上面的问题,我们只需要启动100个线程,每个线程上运行100个协程,这样不仅减少了线程切换开销,而且还能够同时处理10000个读取数据库的任务,很好的解决了上述任务。

知道了协程的工作方式,那么我们再看下使用协程有哪些注意事项。

协程的注意事项

实际上协程并不是什么银弹,协程只有在等待IO的过程中才能重复利用线程,上面我们已经讲过了,线程在等待IO的过程中会陷入阻塞状态,意识到问题没有?

假设协程运行在线程之上,并且协程调用了一个阻塞IO操作,这时候会发生什么?实际上操作系统并不知道协程的存在,它只知道线程,因此在协程调用阻塞IO操作的时候,操作系统会让线程进入阻塞状态,当前的协程和其它绑定在该线程之上的协程都会陷入阻塞而得不到调度,这往往是不能接受的。

因此在协程中不能调用导致线程阻塞的操作。也就是说,协程只有和异步IO结合起来,才能发挥最大的威力。

那么如何处理在协程中调用阻塞IO的操作呢?一般有2种处理方式:

  1. 在调用阻塞IO操作的时候,重新启动一个线程去执行这个操作,等执行完成后,协程再去读取结果。这其实和多线程没有太大区别。
  2. 对系统的IO进行封装,改成异步调用的方式,这需要大量的工作,最好寄希望于编程语言原生支持。

协程对计算密集型的任务也没有太大的好处,计算密集型的任务本身不需要大量的线程切换,因此协程的作用也十分有限,反而还增加了协程切换的开销。

以上就是协程的注意事项。这里顺带一提JavaScript的异步变同步的调用方式,如果协程能够实现该类型的语法,不仅可以把异步操作变为同步,同时在IO操作的时候还能够不占用CPU,写起来非常方便。

异步变同步的调用方式只是一种编程方式,不管是用线程还是用协程都可以实现这种编程方式,好处是不用在处理非常多的回调。

总结

在有大量IO操作业务的情况下,我们采用协程替换线程,可以到达很好的效果,一是降低了系统内存,二是减少了系统切换开销,因此系统的性能也会提升。

在协程中尽量不要调用阻塞IO的方法,比如打印,读取文件,Socket接口等,除非改为异步调用的方式,并且协程只有在IO密集型的任务中才会发挥作用。

协程只有和异步IO结合起来才能发挥出最大的威力。

协程就是java的虚拟线程

死锁

1、死锁的起因

一个进程在其运行的过程中可以提出使用多个资源的要求,仅当全部的资源都可以满足的时候,进程才能继续运行而到达终点,否则该进程因得不到所要求的资源而处于阻塞的状态,当两个或者两个以上的进程同时对于多个互斥资源提出使用的请求的时候,才可能导致死锁。

2、产生死锁的必要条件

  • 互斥控制。进程对其要求的资源进行排他控制,一个资源仅仅能被一个进程独占
  • 非剥夺控制。进程所获得的资源在未被释放之前,不能被其他的进程剥夺,即该进程处于阻塞的状态,它所占用的资源也不能被其他的进程使用,而其他的进程也只能等待该资源的释放。
  • 逐次请求。进程以随意的零星的方式逐次取得资源,而不是集中的一次请求,这样有利于提高资源的利用率。
  • 环路条件。在发生死锁的时候,其有向图必然构成环路,即前一个进程保持着后一个进程所要求的资源。

只要破坏上述条件之一,即可预防死锁的发生


操作系统
http://example.com/2024/04/24/face_OS1/
作者
nianjx
发布于
2024年4月24日
许可协议