# 1. 进程管理之进程实体
# 1.1. 进程
没有配置OS之前,所有资源属于当前运行的程序
- 进程是系统进行资源分配和调度的基本单位
- 进程作为程序独立运行的载体保障程序的正常运行
- 进程使资源的利用率大幅提升
# 1.2. 进程的实体
- 主存中进程形态:标识符、状态、优先级、序计数器、内存指针、上下文数据、IO状态信息、记账信息
- 可分为进程标识符、处理机状态、进程调度信息、进程控制信息等几类
标识符:
唯一标记一个进程(id)
状态:
标记进程状态,如运行态
程序计数器:
进程即将被执行的下一条指令的地址
内存指针:
程序代码、进程数据相关指针
上下文数据:
进程执行时处理器存储的数据
IO状态信息:
被进程IO操作所占用的文件列表(如磁盘、内存、文件等)
记账信息:
使用处理器时间、时钟数总和等
PCB进程控制块:
1. 用于描述和控制进程运行的通用数据结构
2. 经常被读取,常驻内存,存放在系统专门开辟的PCB区域内
# 1.3. 进程 Process 与线程 Thread
- 线程是操作系统进行运行调度的最小单位
- 包含在进程之中,是进程中实际运行工作的单位
- 一个进程可以并发多个线程,每个线程执行不同的任务
- 进程的线程共享进程资源
# 1.4. 进程和线程的对比
进程 | 线程 | |
---|---|---|
资源 | 资源分配的基本单位 | 不拥有资源 |
调度 | 独立调度的基本单位 | 独立调度的最小单位 |
系统开销 | 进程系统开销大 | 线程系统开销小 |
通信 | 进程IPC | 读写同一进程数据通信 |
# 1.5. 并行和并发的区别
- 并行:真正多核
cpu
去执行(python
不能同时利用多个cpu
,只能说是并发) - 并发:看似是并行,其实通过
cpu
的时间片轮转来切换任务,同一时刻还是只能执行一个线程,对外界来说营造了一种同时执行的效果
# 1.6. 什么是线程安全?
- 线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。
- 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据
# 1.7. Python哪些操作是线程安全的?
- 一个操作可以在多线程环境中安全使用,获取正确结果
- 线程安全的操作好比线程是顺序执行而不是并发执行的(
i += 1
不是线程安全) - 一般如果涉及到写操作需要考虑如何让多个线程安全访问数据
# 2. 进程管理之五状态模型
就绪、阻塞、执行、创建、终止
# 2.1. 就绪状态
- 当进程被分配到CPU以外所有其他的资源(只差CPU资源)
- 就绪队列:多个处于就绪状态的进程组成一个队列
# 2.2. 执行状态
- 进程获得CPU,其程序正在执行
- 在单处理机(单核)中,某个时刻只能有一个进程处于执行状态
# 2.3. 阻塞状态
- 进程因某种原因(如其他设备未就绪而无法继续执行)放弃CPU的状态
- 阻塞队列:多个阻塞状态的进程组成的队列
# 2.4. 创建状态
- 进程创建过程:分配PCB => 插入就绪队列
- 创建进程时拥有PCB但其他资源尚未就绪的状态
# 2.5. 终止状态
- 系统清理 => PCB归还
- 进程结束后由系统清理或归还PCB的状态称为终止状态
# 3. 进程管理之进程同步
生产者-消费者模型:
- 生产者A:寄存器从缓存区 取值,然后加1,再放到缓存区;消费者B是减1
- 举例:当并发执行时,A寄存器由10变成11,这时B寄存器从取出10然后减1变成9,然后这两不论谁先放回,都会造成数据不一致
- 缓冲区属于临界资源
哲学家进餐问题:
- 五个人同时拿起左边筷子,然后一起等待右边筷子的释放,但谁都不释放,都饿死了
- 筷子属于临界资源
- 这两个模型根源问题:彼此之间没有通信
# 3.1. 临界资源
- 一些作为共享资源却无法同时被多个线程共同访问的共享资源。
# 3.2. 进程间同步的原则
- 空闲让进:资源无占用,允许使用
- 忙则等待:资源有占用,请求进程等待
- 有限等待:保证有限等待时间能够使用资源
- 让权等待:等待时,进程需要让出 CPU(执行变成阻塞状态)
# 3.3. 进程间同步的方法
- 管道/匿名管道/有名管道(
pipe
) - 信号(
Signal
):比如用户使用Ctrl+c
产生SIGINT
程序终止信号 - 消息队列(
Message
) - 共享内存(
share memory
) - 信号量(
Semphare
) - 套接字(
socket
):最常用的方式,web应用都是这种方式
# 3.4. Python中如何使用多进程?
Python有GIL,可以用多进程实现cpu密集程序
multiprocessing
多进程模块multiprocessing.Process
类实现多进程- 一般在
CPU
密集程序里,避免GIL
的影响
# 3.4.1. 进程同步之共享内存
- 多进程共同使用物理内存
- 由于操作系统的进程管理,进程间的内存空间是独立的
- 进程默认不能访问进程空间之外的内存空间
共享内存特点:
- 共享内存允许不相关的进程访问同一片物理内存
- 共享内存是两个进程之间共享和传递数据最快的方式(常用)
- 共享内存未提供同步机制,需要借助其他机制管理访问(自己设置
can_read
,即加锁/解锁)
步骤:
- 申请共享内存
- 连接到进程空间
- 使用共享内存
- 脱离进程空间&删除
# 3.4.2. 进程同步之Unix域套接字
- 域套接字是高级的进程间通信的方法
- 提供了单机 简单可靠的进程通信同步服务
- 只能在单机使用,不能跨机器使用
- Unix 域套接字提供了类似 网络套接字的功能
服务端使用:
- 创建套接字、绑定、监听套接字,接收并处理信息 客户端使用:
- 创建套接字、连接套接字,发送信息
# 3.5. 线程同步
- 因为进程的线程共享进程资源,所以也需要同步
- 方法:互斥量、读写锁、自旋锁、条件变量
# 3.6. 线程同步的方式
- 互斥量(锁):通过互斥机制防止多个线程同时访问公共资源(缺点:同一时刻只有一个线程访问公共资源)
- 信号量(
Semphare
):控制同一时刻多个线程访问同一个资源的线程数 - 事件(信号):通过通知的方式保持多个线程同步
# 3.6.1. 线程同步之互斥量
当一个线程操作的时候,阻止另一个线程访问这个临界资源(加锁、解锁)
- 生产者、消费者模型的根本:两个线程的指令交叉执行
- 互斥量可以保证先后执行
原子性定义:
- 一系列操作不可被中断的特性
- 这一系列操作要么全部执行完,要么全部没有执行
- 不存在部分执行部分未执行的情况
互斥量:
- 互斥量是最简单的线程同步的方法
- 互斥量(互斥锁),处于两态之一的变量:解锁和加锁
- 两个状态可以保证资源访问的串行
# 3.6.2. 线程同步之自旋锁
使用临界资源之前加锁,使用再解锁,和互斥锁一样
与互斥锁不同点:
- 使用自旋锁的线程会反复检查锁变量是否可用
- 自旋锁不会让出CPU,是一种忙等待状态
- 死循环,等待锁被释放
特点:
- 自旋锁避免了进程或线程上下文切换的开销
- 操作系统内部很多地方使用的是自旋锁
- 自旋锁不适合在单核CPU使用
相当于痴心汉不断地纠缠
# 3.6.3. 线程同步之读写锁
- 临界资源多读少写
- 读取时候并不会改变临界资源的值
读写锁特点:
- 特殊的自旋锁
- 允许多个读者同时访问资源以提高性能
- 对于写操作则是互斥的
读和读不互斥,读和写互斥,写和写互斥
# 3.6.4. 线程同步之条件变量
- 条件变量是相对复杂的线程同步方法
- 条件变量允许线程睡眠,直到满足条件
- 当满足条件时,可以向该线程发送信号,通知唤醒
- 配合互斥量使用
具体:
- 缓冲区小于等于
0
时,不允许消费者消费,消费者必须等待 - 缓冲区满时,不允许生产者往缓冲区生产,生产者必须等待
- 前者情况,当生产者生产一个产品时,唤醒可能等待的消费者
- 后者情况,当消费者消费一个产品时,唤醒可能等待的生产者
对比:
同步方法 | 描述 |
---|---|
互斥锁 | 最简单的一种线程同步方式,会阻塞线程 |
自旋锁 | 避免切换的一种线程同步方法,属于“忙等待” |
读写锁 | 为“读多写少”的资源设计的线程同步方法,可以显著提高性能 |
条件变量 | 相对复杂的一种线程同步方法,有更灵活的使用场景 |
# 3.7. python中如何使用多线程?
threading
模块
threading.Thread
类用来创建线程start()
方法启动线程join()
方法等待线程结束
# 4. Linux进程管理
# 4.1. 进程的类型
- 前台进程
- 后台进程
- 守护进程
# 4.2. 前台进程
具有终端,可以和用户交互的进程(占用了终端shell)
# 4.3. 后台进程
- 没有占用终端(可能有打印,但不影响使用Shell,同时Ctrl+C也不会停止)
- 不和用户交互,优先级比前台进程低
- 将需要执行的命令以&符号结束
# 4.4. 守护进程( daemon
)
- 特殊的后台进程
- 很多在在系统引导的时候启动,一直运行直到系统关闭
- 进程名字一般以d结尾,比如 crond、httpd、sshd、mysqld
进程的标记:
- [ ] 进程ID
- [ ] 进程的状态标记
进程状态标记:
父子进程关系(pstree
命令):
# 4.5. 特殊进程
- ID为 0 的进程是
idle
进程,是系统创建的第一个进程 - ID为 1 的进程是
init
进程,是 0 号进程的子进程,完成系统初始化 - init 进程是所有用户进程的祖先进程
# 4.6. 什么是线程池
- 线程池是存放多个线程的容器
- CPU调度线程执行后不会销毁线程
- 将线程放回线程池 重复利用
# 4.6.1. 为什么使用线程池
- 线程是稀缺资源,不应该频繁创建和销毁
- 架构解耦,线程创建和业务处理解耦,更加优雅
- 线程池是使用线程的最佳实践
# 5. 使用fork
系统调用创建进程
- [ ]
fork
系统调用用于创建进程 - [ ]
fork
创建的进程初始化状态与父进程一样,初始时内存空间一样,后来不一样 - [ ] 系统为
fork
的进程分配新的资源
fork
:
- [ ]
fork
系统调用无参数 - [ ]
fork
会返回两次,分别返回子进程id
和0
- [ ] 第一次由父进程返回,第二次由子进程返回,返回子进程
id
的是父进程,返回0
的是子进程