操作系统 进程与线程(小林)

进程与线程(小林)

1. 进程与线程基本知识

编写的代码只是⼀个存储在硬盘的静态⽂件,通过编译后就会⽣成⼆进制可执⾏⽂件,当我们运⾏这个可执⾏⽂件后,它会被装载到内存中,接着CPU会执⾏程序中的每⼀条指令,那么这个运⾏中的程序,就被称为「进程」(Process)。

由于进程个数多且cpu的内核个数有限,所以采用调度的策略给每个进程一个固定的时间片,时间到了就执行另一个进程。休息的时候要记住执行到哪,方便下次执行。

线程是进程当中的⼀条执⾏流程。同⼀个进程内多个线程之间可以共享代码段、数据段、打开的⽂件等资源,但每个线程各⾃都有⼀套独⽴的寄存器和栈,这样可以确保线程的控制流是相对独⽴的。
本文提纲

1.1 进程

1.1.1 进程的状态

状态
问题1:
创建状态(new) :进程正在被创建,尚未到就绪状态。
就绪状态(ready) :进程已处于准备运⾏状态,即进程获得了除了处理器之外的⼀切所需资源,⼀旦得到处理器资源(处理器分配的时间⽚)即可运⾏。
运⾏状态(running) :进程正在处理器上上运⾏(单核 CPU 下任意时刻只有⼀个进程处于运⾏状态)。
阻塞状态(waiting) :⼜称为等待状态,进程正在等待某⼀事件⽽暂停运⾏如等待某资源为可⽤或等待 IO 操作完成。即使处理器空闲,该进程也不能运⾏。
结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运⾏。
问题2:
如果有⼤量处于阻塞状态的进程,进程可能会占⽤着物理内存空间,显然不是我们所希望的,毕竟物理内存空间是有限的,被阻塞状态的进程占⽤着物理内存就⼀种浪费物理内存的⾏为。所以,在虚拟内存管理的操作系统中,通常会把阻塞状态的进程的物理内存空间换出到硬盘,等需要再次运⾏的时候,再从硬盘换⼊到物理内存。
描述进程没有占⽤实际的物理内存空间的情况,这个状态就是挂起状态。
另外,挂起状态可以分为两种:
1、阻塞挂起状态:进程在外存(硬盘)并等待某个事件的出现;
2、就绪挂起状态:进程在外存(硬盘),但只要进⼊内存,即刻⽴刻运⾏;
在这里插入图片描述
导致进程挂起的原因不只是因为进程所使⽤的内存空间不在物理内存,还包括如下情况
1、通过 sleep 让进程间歇性挂起,其⼯作原理是设置⼀个定时器,到期后唤醒进程。
2、⽤户希望挂起⼀个程序的执⾏,⽐如在 Linux 中⽤ Ctrl+Z 挂起进程

1.12进程的控制结构

PCB 是进程存在的唯⼀标识,这意味着⼀个进程的存在,必然会有⼀个 PCB,如果进程消失了,那么
PCB 也会随之消失。问题1:主要包括:
进程描述信息:
进程标识符:标识各个进程,每个进程都有⼀个并且唯⼀的标识符;
⽤户标识符:进程归属的⽤户,⽤户标识符主要为共享和保护服务;
进程控制和管理信息:
进程当前状态,如 new、ready、running、waiting 或 blocked 等;
进程优先级:进程抢占 CPU 时的优先级;
资源分配清单:
有关内存地址空间或虚拟地址空间的信息,所打开⽂件的列表和所使⽤的 I/O 设备信息。
CPU 相关信息:
CPU 中各个寄存器的值,当进程被切换时,CPU 的状态信息都会被保存在相应的 PCB 中,以便进程
重新执⾏时,能从断点处继续执⾏。
问题2:PCB通常是通过链表的⽅式进⾏组织,把具有相同状态的进程链在⼀起,组成各种队列。⽐如:
将所有处于就绪状态的进程链在⼀起,称为就绪队列;
把所有因等待某事件⽽处于等待状态的进程链在⼀起就组成各种阻塞队列;

1.13进程的上下文切换

各个进程之间是共享 CPU 资源的,在不同的时候进程之间需要切换,让不同的进程可以在 CPU 执⾏,那
么这个⼀个进程切换到另⼀个进程运⾏,称为进程的上下⽂切换。
所以,进程的上下⽂切换不仅包含了虚拟内存、栈、全局变量等⽤户空间的资源,还包括了内核堆栈、寄
存器等内核空间的资源。通常,会把交换的信息保存在进程的 PCB,当要运⾏另外⼀个进程的时候,我们需要从这个进程的 PCB取出上下⽂,然后恢复到 CPU 中,这使得这个进程可以继续执⾏。

1.2 线程

1.21为什么使用线程?

对于多进程的这种⽅式,会存在问题:
1、进程之间如何通信,共享数据?
2、维护进程的系统开销较⼤,如创建进程时,分配资源、建⽴ PCB;终⽌进程时,回收资源、撤销
PCB;进程切换时,保存当前进程的状态信息;
线程( Thread ),线程之间可以并发运⾏且共享相同的地址空间。

1.22线程与进程的比较

⼀个进程中可以有多个线程,多个线程共享进程的堆和⽅法区 (JDK1.8 之后的元空间)资源,但是每个线程有⾃⼰的程序计数器、虚拟机栈 和 本地⽅法栈。
总结: 线程是进程划分成的更⼩的运⾏单位,⼀个进程在其执⾏的过程中可以产⽣多个线程。线程和进程最⼤的不同在于基本上各进程是独⽴的,⽽各线程则不⼀定,因为同⼀进程中的线程极有可能会相互影响。线程执⾏开销⼩,但不利于资源的管理和保护;⽽进程正相反。

1.23线程的上下文切换

当两个线程不是属于同⼀个进程,则切换的过程就跟进程上下⽂切换⼀样;
当两个线程是属于同⼀个进程,因为虚拟内存是共享的,所以在切换时,虚拟内存这些资源就保持不动,只需要切换线程的私有数据、寄存器等不共享的数据;所以,线程的上下⽂切换相⽐进程,开销要⼩很多。

1.3 调度

1.31 调度原则

原则⼀ :为了提⾼ CPU 利⽤率,在这种发送I/O 事件致使 CPU 空闲的情况下,调度程序需要从就绪队列中选择⼀个进程来运⾏。
原则⼆ :要提⾼系统的吞吐率,调度程序要权衡⻓任务和短任务进程的运⾏完成数量。
原则三 :从进程开始到结束的过程中,实际上是包含两个时间,分别是进程运⾏时间和进程等待时间,这两个时间总和就称为周转时间。进程的周转时间越⼩越好,如果进程的等待时间很⻓⽽运⾏时间很短,那周转时间就很⻓,这不是我们所期望的,调度程序应该避免这种情况发⽣。
原则四 :就绪队列中进程的等待时间也是调度程序所需要考虑的原则。
原则五 :对于交互式⽐较强的应⽤,响应时间也是调度程序需要考虑的原则。
在这里插入图片描述

1.32 调度算法

01、先来先服务调度算法:每次从就绪队列选择最先进⼊队列的进程,然后⼀直运⾏,直到进程退出或被阻塞,才会继续从队列中选择第⼀个进程接着运⾏。
FCFS 对⻓作业有利,适⽤于 CPU 繁忙型作业的系统,⽽不适⽤于 I/O 繁忙型作业的系统。
02、最短作业优先调度算法:它会优先选择运⾏时间最短的进程来运⾏,这有助于提⾼系统的吞吐量。
这显然对⻓作业不利。
03、优先级调度算法:为每个流程分配优先级,⾸先执⾏具有最⾼优先级的进程,依此类推。具有相同优先级的进程以 FCFS ⽅式执⾏。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
04、时间⽚轮转调度算法:每个进程被分配⼀个时间段,称为时间⽚(Quantum),即允许该进程在该时间段中运⾏。
05、多级反馈队列调度算法:多级反馈队列(Multilevel Feedback Queue)调度算法是「时间⽚轮转算法」和「最⾼优先级算法」的综合和发展。
设置了多个队列,赋予每个队列不同的优先级,每个队列优先级从⾼到低,同时优先级越⾼时间⽚越短;
新的进程会被放⼊到第⼀级队列的末尾,按先来先服务的原则排队等待被调度,如果在第⼀级队列规定的时间⽚没运⾏完成,则将其转⼊到第⼆级队列的末尾,以此类推,直⾄完成;
当较⾼优先级的队列为空,才调度较低优先级的队列中的进程运⾏。如果进程运⾏时,有新进程进⼊较⾼优先级的队列,则停⽌当前运⾏的进程并将其移⼊到原队列末尾,接着让较⾼优先级的进程运⾏;

2. 进程间通信

进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。

2.1 管道

有两种管道匿名管道和FIFO。管道是特殊类型的文件,在满足先入先出的原则条件下可以进行读写,但不能进行定位读写。匿名管道是单向的,只能在有亲缘关系的进程间通信;有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
局限性:管道的通信⽅式是效率低的,因此管道不适合进程间频繁地交换数据。

2.2 消息队列

消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。与管道(⽆名管道:只存在于内存中的⽂件;命名管道:存在于实际的磁盘介质或者⽂件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除⼀个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不⼀定要以先进先出的次序读取,也可以按消息的类型读取.⽐ FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载⽆格式字 节流以及缓冲区大小受限等缺点。
局限性:因为在内核中每个消息体都有⼀个最⼤⻓度的限制,消息队列不适合⽐较⼤数据的传输。消息队列通信过程中,存在⽤户态与内核态之间的数据拷贝开销。

2.3 共享内存

现代操作系统,对于内存管理,采⽤的是虚拟内存技术,也就是每个进程都有⾃⼰独⽴的虚拟内存空间,不同进程的虚拟内存映射到不同的物理内存中。共享内存的机制,就是拿出⼀块虚拟地址空间来,映射到相同的物理内存中。这样这个进程写⼊的东⻄,另外⼀个进程⻢上就能看到了,都不需要拷贝来拷贝去,传来传去,大大提高了进程间通信的速度。

2.4 信号量

⽤了共享内存通信⽅式,带来新的问题,那就是如果多个进程同时修改同⼀个共享内存,很有可能就冲突了。例如两个进程都同时写⼀个地址,那先写的那个进程会发现内容被别⼈覆盖了。为了防⽌多进程竞争共享资源,⽽造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被⼀个进程访问。正好,信号量就实现了这⼀保护机制。信号量其实是⼀个整型的计数器,主要⽤于实现进程间的互斥与同步,当一个进程对共享内存操作时,信号量变为负,组织别的进行进行操作,当操作完成后,再将信号量变为正。

2.5 信号

上⾯说的进程间通信,都是常规状态下的⼯作模式。对于异常情况下的⼯作模式,就需要⽤「信号」的⽅式来通知进程。信号⽤于通知接收进程某个事件已经发⽣,接收进程处理这个事件。

2.6 Sockets

此⽅法主要⽤于在客户端和服务器之间通过⽹络进⾏通信。套接字是⽀持TCP/IP 的⽹络通信的基本操作单元,可以看做是不同主机之间的进程进⾏双向通信的端点,简单的说就是通信的两⽅的⼀种约定,⽤套接字中的相关函数来完成通信过程。

3 多线程同步

线程同步是两个或多个共享关键资源的线程的并发执⾏。应该同步线程以避免关键的资源使
⽤冲突。操作系统⼀般有下⾯三种线程同步的⽅式:
1、临界区:在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。它并不是核心对象,不是属于操作系统维护的,而是属于进程维护的。
2、互斥对象:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程同时访问。当前拥有互斥对象的线程处理完任务后必须将线程交出,以便其他线程访问该资源。
3、信号量(Semphares) :它允许同⼀时刻多个线程访问同⼀资源,但是需要控制同⼀时刻访问此资源的最⼤线程数量。
4、事 件: 通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作。

4 死锁

在多线程编程中,我们为了防⽌多线程竞争共享资源⽽导致数据错乱,都会在操作共享资源之前加上互斥锁,只有成功获得到锁的线程,才能操作共享资源,获取不到锁的线程就只能等待,直到锁被释放。那么,当两个线程为了保护两个不同的共享资源⽽使⽤了两个互斥锁,那么这两个互斥锁应⽤不当的时候,可能会造成两个线程都在等待对⽅释放锁,在没有外⼒的作⽤下,这些线程会⼀直相互等待,就没办法继续运⾏,这种情况就是发⽣了死锁。
避免死锁问题发生:最常⻅的并且可⾏的就是使⽤资源有序分配法,让两个线程获取资源的顺序一致就会避免死锁的发生。

5 锁的类别

在这里插入图片描述
1、互斥锁加锁失败后,线程会释放 CPU ,给其他线程;
2、⾃旋锁加锁失败后,线程会忙等待,直到它拿到锁;
所以,如果你能确定被锁住的代码执⾏时间很短,就不应该⽤互斥锁,⽽应该选⽤⾃旋锁,否则使⽤互斥锁。
3、读写锁适⽤于能明确区分读操作和写操作的场景。读写锁在读多写少的场景,能发挥出优势。公平读写锁⽐较简单的⼀种⽅式是:⽤队列把获取锁的线程排队,不管是写线程还是读线程都按照先进先出的原则加锁即可,这样读线程仍然可以并发,也不会出现「饥饿」的现象。
4、前⾯提到的互斥锁、⾃旋锁、读写锁,都是属于悲观锁。悲观锁做事⽐较悲观,它认为多线程同时修改共享资源的概率⽐较⾼,于是很容易出现冲突,所以访问共享资源前,先要上锁。
那相反的,如果多线程同时修改共享资源的概率⽐较低,就可以采⽤乐观锁。乐观锁做事⽐较乐观,它假定冲突的概率很低,它的⼯作⽅式是:先修改完共享资源,再验证这段时间内
有没有发⽣冲突,如果没有其他线程在修改资源,那么操作完成,如果发现有其他线程已经修改过这个资源,就放弃本次操作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值