进程与线程
进程是资源分配的基本单位。一个进程中可以有多个线程,它们共享进程资源。
线程是独立调度的基本单位。
区别:
- 资源:进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
- 别把内存空间和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。线程拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源。
- 调度:线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
- 系统开销:由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
- 通信:线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
进程状态切换
- 就绪状态(ready):等待被调度
- 运行状态(running)
- 阻塞状态(waiting):等待资源
PS:
- 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
- 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。
进程调度算法
- 批处理系统
- 先来先服务 first-come first-serverd(FCFS)
- 短作业优先 shortest job first(SJF)
- 最短剩余时间优先 shortest remaining time next(SRTN)
- 交互式系统
- 时间片轮转
- 优先级调度
- 多级反馈队列
进程同步
同步: 多个进程因为合作产生的直接制约关系,使得进程有一定的先后执行关系。
互斥: 多个进程在同一时刻只有一个进程能进入临界区。
四种方法:
- 临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
- 互斥量(Mutex):为协调共同对一个共享资源的单独访问而设计的。
- 信号量(Semaphore):为控制一个具有有限数量用户资源而设计。是一个整型变量,可以对其执行 down 和 up 操作,也就是常见的 P 和 V 操作。
- 互斥量是信号量的一种特殊情况,当信号量的最大资源数=1就是互斥量了。
- 事件(Event): 用来通知线程有一些事件已发生,从而启动后继任务的开始。
经典同步问题
- 读者-写者问题
- 哲学家进餐问题
进程通信
进程同步: 控制多个进程按一定顺序执行。
进程通信: 进程间传输信息。
- 管道 pipe
- 只支持半双工通信(单向交替传输);
- 只能在父子进程(有血缘关系)或者兄弟进程中使用。
- 命名管道 FIFO
- 去除了管道只能在父子进程中使用的限制。
- 常用于客户-服务器应用程序中,FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
- 信号量 semophore
- 它是一个计数器,用于为多个进程提供对共享数据对象的访问。
- 常作为一种锁机制。
- 消息队列
- 消息队列可以独立于读写进程存在,从而避免了 FIFO 中同步管道的打开和关闭时可能产生的困难;
- 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
- 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
- 信号 signal
- 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
- 共享存储
- 允许多个进程共享一个给定的存储区。因为数据不需要在进程之间复制,所以这是最快的一种 IPC。
- 需要使用信号量用来同步对共享存储的访问。
- 套接字 Socket
- 可用于不同机器间的进程通信。
- Linux中使用 socket() 函数来创建一个网络连接,返回值就是文件描述符。
线程通信
同一进程中的线程因属同一地址空间,可直接通信,不必通过操作系统。
- 锁机制:包括互斥锁、条件变量、读写锁
- 互斥锁提供了以排他方式防止数据结构被并发修改的方法。
- 读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
- 条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
- 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
- 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
死锁
多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗的讲就是两个或多个进程无限期的阻塞、相互等待的一种状态。
死锁产生的四个条件(有一个条件不成立,则不会产生死锁):
- 互斥条件:一个资源一次只能被一个进程使用。
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得资源保持不放。
- 不剥夺条件:进程获得的资源,在未完全使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成一种头尾相接的环形等待资源关系。
解决死锁:
- 预防死锁
- 资源一次性分配
- 可剥夺资源
- 资源有序分配法
- 避免死锁
- 允许进程动态地申请资源
- 银行家算法
- 检测死锁
- 建立资源分配表和进程等待表
- 解除死锁
- 剥夺资源
- 撤消进程
Socket
IO 模型
一个输入操作通常包括两个阶段:
- 等待数据准备好
- 从内核向进程复制数据
Unix 有五种 I/O 模型:
- 阻塞式 I/O
- 非阻塞式 I/O
- I/O 复用(select 和 poll)
- 信号驱动式 I/O(SIGIO)
- 异步 I/O(AIO)
IO多路复用
是指内核一旦发现进程指定的一个或者多个IO条件准备读取,它就通知该进程。
- 与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小。
- epoll跟select都能提供多路I/O复用的解决方案