进程与线程

一、关于进程:

1.1 什么是进程?

进程是具有独立功能的程序在某个数据集合上的一次运行活动,也是操作系统进行**资源分配和保护的基本单位。**有挂起状态

1.2 进程的组成部分:

进程由四个部分组成:

①: 程序块
②: 数据块: 程序和数据刻画的是静态特征
③: 进程控制块 (Process Control Block, PCB) : 每个进程只有一个进程控制块Process Control Block (进程描述符), 包含管理进程所需的全部信息.
④: 核心栈: 进程运行过程中产生中断或者执行系统调用时又要运行操作系统内核函数 , 核心栈就是存放内核函数在工作时产生的信息

**PCB包含三类信息: **
①: 标识信息: 数值型进程号0~32767 是进程存在的唯一标识
②: 现场信息: 包括通用寄存器, 控制寄存器, 栈指针, 程序状态字 进程让出处理器时,必须将此时的现场信息保存到PCB
③: 控制信息: 用于管理和调度进程

1.3 进程状态的切换:

进程的三种基本状态:

  • 运行状态:获得CPU的进程处于此状态,对应的程序在CPU上运行着

  • 阻塞状态:等待资源; 由于进程等待某种条件(如等待I/O操作的完成,或等待另一个进程发来消息(即进程同步)),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行

  • 就绪状态:等待被调度 ; 进程已获得除CPU外的所需资源,由于其他进程占用CPU而暂时无法运行的一种状态

注意: **
①: 只有就绪态和运行态可以相互转换,其它的都是单向转换。
就绪状态的进程通过
调度算法从而获得 CPU 时间,转为运行状态;
而运行状态的进程,在分配给它的
CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
②: 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是
该资源不包括 CPU 时间,**缺少 CPU 时间会从运行态转换为就绪态。

1.4 进程的特点:

动态性: 程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
共享性: 多个不同的进程,可以执行相同的程序, 进程和程序不是一 一对应的
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
独立性: 每个进程是操作系统中的一个独立的实体, 有独立的虚存空间,程序计数器,内部状态
并发性: 在单处理器系统中可并发执行, 多处理器环境中并行执行.

1.5 进程的挂起:

目的: 平滑系统负荷
什么时候挂起? 当系统资源尤其是内存资源已经不能满足进程运行的要求时,必须把某些进程挂起.
特点: 将不参与低级调度, 直到它们被对换到内存, 该进程不能立即执行
**如何结束挂起状态? ** 只能由操作系统或者父进程发出

1.6 进程挂起和激活:

挂 起 原 语 既 可 以 由 进 程 自 己 也 可 以 由 其 他 进 程 调 用, 激 活 原 语 只 能 由 其 他 进 程 调 用

1.7 进程阻塞和唤醒:

阻塞: 进程让出处理器,转而等待一个事件,如等待资源,等待IO操作完成, 等待事件发生.
进程通常调用阻塞原语来阻塞自己, 因此阻塞是自主行为
唤醒: 等待事件完成时会产生中断,激活操作系统,在操作系统控制下,与其相关的另一个进程调用唤醒原语将阻塞进程唤醒.

二、关于线程:

1.1 什么是线程?

是进程中能并发执行的实体, 是进程的组成部分,有时被称为轻量级进程(Lightweight Process,LWP), 是处理器调度和分派的基本单位, 是一条执行路径,有独立的程序计数器 无挂起状态

1.2 线程的重要特征:

①: 一个进程中可以有多个线程,它们共享进程资源。
②: 线程是轻量级的进程
③: 线程是由进程创建的(寄生在进程)
④: 线程没有独立的地址空间(内存空间)
⑤: 线程是系统独立调度和分配的基本单位
⑥: 可并发执行
⑦: 线程是一种轻型实体
线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。TCB包括以下信息:

  • 线程状态
  • 存放每个线程的局部变量主存区
  • 访问同一个进程中的主存和其它资源
  • 当线程不运行时,被保存的现场资源

1.3 创建线程的不同方式:

有4种方式可以用来创建线程:
①继承Thread类
②实现Runnable接口
③应用程序可以使用Executor框架来创建线程池
实现Runnable接口这种方式更受欢迎,因为这不需要继承Thread类。在应用设计中已经继承了别的对象的情况下,这需要多继承(而Java不支持多继承),只能实现接口。同时,线程池也是非常高效的,很容易实现和使用。
④还有一种方式是实现Callable接口

1.4 进程状态的切换:

新建( new ):新创建了一个线程对象。

可运行( runnable ):线程对象创建后,其他线程(比如 main 线程)调用了该对象 的 start ()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获 取 cpu 的使用权 。

运行( running ):可运行状态( runnable )的线程获得了 cpu 时间片( timeslice ) ,执行程序代码。

阻塞( block ):阻塞状态是指线程因为某种原因放弃了 cpu 使用权,也即让出了 cpu timeslice ,暂时停止运行。直到线程进入可运行( runnable )状态,才有 机会再次获得 cpu timeslice 转到运行( running )状态。

阻塞的情况分三种:

(一). 等待阻塞:

运行( running )的线程执行 o . wait ()方法, JVM 会把该线程放 入等待队列( waitting queue )中。

(二). 同步阻塞:

运行( running )的线程在获取对象的同步锁时,若该同步锁 被别的线程占用,则 JVM 会把该线程放入锁池( lock pool )中。

(三). 其他阻塞:

运行( running )的线程执行 Thread . sleep ( long ms )或 t . join ()方法,或者发出了 I / O 请求时, JVM 会把该线程置为阻塞状态。

当 sleep ()状态超时、 join ()等待线程终止或者超时、或者 I / O 处理完毕时,线程重新转入可运行( runnable )状态。

死亡( dead ):线程 run ()、 main () 方法执行结束,或者因异常退出了 run ()方法,则该线程结束生命周期。死亡的线程不可再次复生。

1.4 优点:

快速切换 : 同一个进程中的多线程只需要改变堆栈和寄存器, 地址空间不变
通信容易: 不必经过内核, 可自由访问全局数据,自动共享进程的内存和文件
减少管理开销: 线程的创建和撤销工作比进程少很多,并且无需再分配存储空间,和各种资源
并发程度提高:

三、进程与线程的区别:

3.1 就资源而言:

进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。

3.2 就调度而言:

线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。

3.3 就系统开销而言:

由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。

3.4 就通信而言:

线程间可以通过直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。但是进程通信需要借助 IPC。

3.5 就组成而言:

进程可分为两部分: 资源集合 和 线程集合
进程封装管理信息,线程封装执行信息

四、进程的调度算法:

不同环境的调度算法目标不同,因此需要针对不同环境来讨论调度算法。

4.1 批处理系统

批处理系统没有太多的用户操作,在该系统中,调度算法目标是保证吞吐量和周转时间(从提交到终止的时间)。

①: 先来先服务 first-come first-serverd(FCFS)
按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。

②: 短作业优先 shortest job first(SJF)
按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。

③: 最短剩余时间优先 shortest remaining time next(SRTN)
按估计剩余时间最短的顺序进行调度。

4.2 交互式系统:

交互式系统有大量的用户交互操作,在该系统中调度算法的目标是快速地进行响应。
④: 时间片轮转
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。

时间片轮转算法的效率和时间片的大小有很大关系:

  • 因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。
  • 而如果时间片过长,那么实时性就不能得到保证。

⑤: 优先级调度

为每个进程分配一个优先级,按优先级进行调度。
为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。

⑥: 多级反馈队列

一个进程需要执行 100 个时间片,如果采用时间片轮转调度算法,那么需要交换 100 次。

多级队列是为这种需要连续执行多个时间片的进程考虑,它设置了多个队列,每个队列时间片大小都不同,例如 1,2,4,8,…。进程在第一个队列没执行完,就会被移到下一个队列。这种方式下,之前的进程只需要交换 7 次。

每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。

可以将这种调度算法看成是时间片轮转调度算法和优先级调度算法的结合。

4.3 实时系统

实时系统要求一个请求在一个确定时间内得到响应。
分为硬实时和软实时,前者必须满足绝对的截止时间,后者可以容忍一定的超时。

五、进程同步:

临界区:

对临界资源进行访问的那段代码称为临界区。
为了互斥访问临界资源,每个进程在进入临界区之前,需要先进行检查。

同步与互斥:

同步:多个进程按一定顺序执行;
互斥:多个进程在同一时刻只有一个进程能进入临界区。

信号量:

信号量(Semaphore)是一个整型变量,可以对其执行 P 和 V 操作。

  • P: 如果信号量大于 0 ,执行 -1 操作;如果信号量等于 0,进程睡眠,等待信号量大于 0;
  • V :对信号量执行 +1 操作,唤醒睡眠的进程让其完成 P 操作。

P, V操作需要被设计成原语,不可分割,通常的做法是在执行这些操作的时候屏蔽中断。

如果信号量的取值只能为 0 或者 1,那么就成为了 互斥量(Mutex) ,0 表示临界区已经加锁,1 表示临界区解锁。

typedef int semaphore;
semaphore mutex = 1;
void P1() {
    P(&mutex);
    // 临界区
    V(&mutex);
}

void P2() {
    P(&mutex);
    // 临界区
    V(&mutex);
}

使用信号量实现生产者-消费者问题

问题描述:使用一个缓冲区来保存物品,只有缓冲区没有满,生产者才可以放入物品;只有缓冲区不为空,消费者才可以拿走物品。

因为缓冲区属于临界资源,因此需要使用一个互斥量 mutex 来控制对缓冲区的互斥访问。

为了同步生产者和消费者的行为,需要记录缓冲区中物品的数量。数量可以使用信号量来进行统计,这里需要使用两个信号量:empty 记录空缓冲区的数量,full 记录满缓冲区的数量。其中,empty 信号量是在生产者进程中使用,当 empty 不为 0 时,生产者才可以放入物品;full 信号量是在消费者进程中使用,当 full 信号量不为 0 时,消费者才可以取走物品。

**注意,不能先对缓冲区进行加锁,再测试信号量。**也就是说,不能先执行 P(mutex) 再执行 P(empty)。如果这么做了,那么可能会出现这种情况:生产者对缓冲区加锁后,执行 P(empty) 操作,发现 empty = 0,此时生产者睡眠。消费者不能进入临界区,因为生产者对缓冲区加锁了,消费者就无法执行 V(empty) 操作,empty 永远都为 0,导致生产者永远等待下,不会释放锁,消费者因此也会永远等待下去。

#define N 100
typedef int semaphore;
semaphore mutex = 1;
semaphore empty = N;
semaphore full = 0;

void producer() {
    while(TRUE) {
        int item = produce_item();
        P(&empty);
        P(&mutex);
        insert_item(item);
        V(&mutex);
        V(&full);
    }
}

void consumer() {
    while(TRUE) {
        P(&full);
        P(&mutex);
        int item = remove_item();
        consume_item(item);
        V(&mutex);
        V(&empty);
    }
}

六、关于管程:

使用信号量机制实现的生产者消费者问题需要客户端代码做很多控制,而管程把控制的代码独立出来,不仅不容易出错,也使得客户端代码调用更容易。
c 语言不支持管程

管程有一个重要特性:
在一个时刻只能有一个进程使用管程。
进程在无法继续执行的时候不能一直占用管程,否则其它进程永远不能使用管程。
管程引入了 条件变量 以及相关的操作:wait()signal() 来实现同步操作。
对条件变量执行 wait() 操作会导致调用进程阻塞,把管程让出来给另一个进程持有。
signal() 操作用于唤醒被阻塞的进程。

七、进程的通信:

进程同步与进程通信很容易混淆,它们的区别在于:
进程同步:控制多个进程按一定顺序执行;
进程通信:进程间传输信息

进程通信是一种手段,而进程同步是一种目的。也可以说,为了能够达到进程同步的目的,需要让进程进行通信,传输一些进程同步所需要的信息。

八、关于管道:

管道是通过调用 pipe 函数创建的,fd[0] 用于读,fd[1] 用于写。

它具有以下限制:

  • 只支持半双工通信(单向交替传输);

  • 只能在父子进程中使用。

九、同步方法和同步代码块的区别是什么?

①: 同步方法默认用this或者当前类class对象作为锁;
同步代码块可以选择以什么来加锁,比同步方法要更细颗粒度,我们可以选择只同步会发生同步问题的部分代码而不是整个方法;
②: 同步方法使用关键字 synchronized修饰方法,
而同步代码块主要是修饰需要进行同步的代码,用 synchronized(object){代码内容}进行修饰;
参考链接:
https://github.com/CyC2018/CS-Notes/blob/master/docs/notes/计算机操作系统 - 进程管理.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值