文章目录
意义 : 充分利用计算机的资源提高程序的运行效率
定义 : 通过应用程序利用计算机的多个核心达到同时执行多个任务的目的,一次提高计算机运行效率。
实施方案 : 多进程 多线程
并行 : 多个计算机核心在同时处理多个任务,这时多个任务间是并行关系。
并发 : 同时处理多个任务,内核在多个任务间不断的切换,达到好像都在处理运行的效果。但实际一个时间点内核只能处理其中一个任务。
进程 (Process)
定义 : 程序在计算机中的一次运行过程
程序 : 是一个可执行的文件,是静态的占有磁盘空间,不占有计算机的运行资源
进程 : 进程是一个动态过程的描述,占有计算机的资源,有一定的生命周期
- 同一个程序的不同运行过程是不同的进程,占用资源和生命周期都不一样。
进程的创建流程
1.用户空间通过运行程序或者调用接口发起创建进程
2.操作系统接受用户请求,开始创建进程
3.操作系统分配计算机资源,确定进程状态,开辟进程空间等工作
4.操作系统将创建好的进程提供给应用程序使用
cpu时间片
如果一个进程占有计算机核心,我们称为改进程占有计算机cpu时间片。
- 多个任务之间是争夺cpu的关系
- 谁占有cpu最终是操作系统决定
PCB (进程控制块)
在内存中开辟的一块空间,用来记录进程的信息
- 进程控制块是操作系统查找识别进程的标志
进程信息 : ps -aux
PID(process ID) : 在操作系统中每个进程都有一个唯一的ID号用来区别于其他进程。ID号由操作系统自动分配,是一个大于0的整数
父子进程 : 在系统中除了初始化进程,每一个进程都有一个父进程,可能有0个或者多个子进程。由此形成父子进程关系。
查看进程树 : pstree
查看父进程PID: ps -ajx
进程的状态
三态
* 就绪态 : 进程具备执行条件,等待系统分配资源
* 运行态 : 进程占有cpu处于运行状态
* 等待态 : 进程暂时不具备执行条件,阻塞等待满 足条件后再执行
五态 (三态基础上增加新建态,终止态)
* 新建态 : 创建一个新的进程,获取资源的过程
* 终止态 : 进程执行结束,资源释放回收的过程
ps -aux —> STAT
S 等待态 (可中断等待)
D 等待态 (不可中断等待)
T 等待态 (暂停状态)
R 运行态 (包含就绪态)
Z 僵尸进程
< 高优先级进程
N 优先级较低
l 有子进程的
s 会话组组长
- 前台进程
进程优先级
作用 : 决定了一个进程的执行权限和占有资源的优先程度
查看进程优先级
top 动态查看系统中的进程信息, 用<>翻页
取值范围 -20 – 19 -20优先级最高
使用指定的优先级运行程序
nice : 指定运行的优先级
e.g. nice -9 ./while.py 以优先级9运行
nice --9 ./while.py 以-9优先级运行
进程特征
- 进程之间运行互不影响 各自独立运行
- 进程是操作系统资源分配的最小单位
- 每个进程空间独立,各自占有一定的虚拟内存
要求 :
- 什么是进程,进程和程序的区别
- 了解进程特征
- 清楚进程每种状态,以及状态之间的转换关系
多进程编程
import os
pid = os.fork()
功能 : 创建新的进程
参数 : 无
返回值: 失败返回一个负数
成功 : 在原有进程中返回新的进程的PID号,在新的进程中返回0
- 子进程会复制父进程全部代码段,包括fork之前产生的内存空间
- 子进程从fork的下一句开始执行,与父进程互不干扰
- 父子进程的执行顺序是不一定的,父子进程公用一个终端显示
- 父子进程通常会根据fork返回值得差异选择执行不同的代码。所以if结构几乎是fork的固定搭配
- 父子进程空间独立,操作的都是本空间的内容,互不影响
- 子进程也有自己的特性,比如PID号,PCB,命令集等
进程相关函数
获取进程PID
os.getpid()
功能 : 获取当前进程的进程号
返回值 : 返回进程号
os.getppid()
功能 : 获取当前进程父进程的PID号
返回值 : 返回进程号
进程退出
os._exit(status)
功能 : 进程退出
参数 : 进程的退出状态
sys.exit([status])
功能 : 进程退出
参数 : 数字表示退出状态,不写默认为0
字符串,表示退出时打印的内容
- sys.exit 可以通过捕获 SystemExit异常阻止退出
孤儿进程: 父进程先于子进程退出,此时子进程就称为孤儿进程。
- 孤儿进程会被操作系统指定的进程收养,系统进程就成为孤儿进程的新的父进程
僵尸进程: 子进程先于父进程退出,但是父进程没有处理子进程的退出状态,此时子进程就会成为僵尸进程。
- 僵尸进程会存留少量PCB信息在内存中,大量的僵尸进程会消耗系统资源,应该避免僵尸进程产生
如何避免僵尸进程产生
* 处理子进程退出状态
pid,status = os.wait()
功能 :在父进程中阻塞等待处理子进程退出
返回值: pid 退出的子进程的PID号
status 获取子进程退出状态
pid,status = os.waitpid(pid,option)
功能 :在父进程中阻塞等待处理子进程退出
参数 : pid -1 表示等待任意子进程退出
>0 表示等待对应PID号的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞
返回值: pid 退出的子进程的PID号
status 获取子进程退出状态
waitpid(-1,0) ===> wait()
* 让父进程先退出
1. 父进程创建子进程等待子进程退出
2. 子进程创建二级子进程后立即退出
3. 二级子进程称为孤儿,和原来的父进程各自执行事件
multiprocessing 模块创建进程
- 需要将要执行的事情封装为函数
- 使用multiprocessing模块中Process类创建进程对象
- 通过对象属性设置和Process的初始化函数对进程进行设置,绑定要执行的函数
- 启动进程,会自动执行进程绑定的函数
- 完成进程的回收
Process()
功能 : 创建进程对象
参数 : name 进程名称 Process-1
target 绑定函数
args 元组 给target函数按照位置传参
kwargs 字典 给target函数按照键值对传参
p.start()
功能:启动进程
* target函数会自动执行,此时进程真正被创建
p.join([timeout])
功能 : 阻塞等待回收子进程
参数 : 超时时间
* 使用multiprocessing创建子进程,同样子进程复制父进程的全部代码段,
* 父子进程各自执行互不影响,父子进程有各自的运行空间
* 如果不使用join回收子进程则子进程退出后会成为僵尸进程
* 使用multiprocessing创建子进程往往父进程只是用来创建进程回收进程
Process进程对象属性
p.start()
p.join()
p.is_alive()
判断进程生命周期状态,处于生命周期得到True否则返回False
p.name 进程名称 默认为Process-1
p.pid 进程的PID号
p.daemon
默认状态False 主进程退出不会影响子进程执行
如果设置为True 则子进程会随着主进程结束而结束
* 要在start前设置
* 一般不和join一起使用
创建自定义进程类
- 继承Process
- 编写自己的__init__ ,同时加载父类init方法
- 重写run方法,可以通过生成的对象调用start自动运行
优点 : 可以使用计算机多核,进行任务的并发执行,提高执行效率空间独立,数据安全 运行不受其他进程影响,创建方便
缺点 : 进程的创建和删除消耗的系统资源较多
进程池技术
产生原因 : 如果有大量任务需要多进程完成,则可能需要频繁的创建删除进程,给进算计带来较多的资源消耗。
原理 : 创建适当的进程放入进程池,用来处理待处理事件,处理完毕后进程不销毁,仍然在进程池中等待处理其他事件。 进程的复用降低了资源的消耗
使用方法
- 创建进程池,在池内放入适当的进程
- 将事件加入到进程池等待队列
- 不断取进程执行事件,直到所有事件执行完毕
- 关闭进程池,回收进程
from multipeocessing import Pool
Pool(processes)
功能 : 创建进程池对象
参数 :表示进程池中有多少进程
pool.apply_async(func,args,kwds)
功能 : 将事件放入到进程池队列
参数 : func 事件函数
args 以元组形式给func传参
kwds 以字典形式给func传参
返回值 : 返回一个代表进程池事件的对象
pool.apply(func,args,kwds)
功能 : 将事件放入到进程池队列
参数 : func 事件函数
args 以元组形式给func传参
kwds 以字典形式给func传参
pool.close()
功能: 关闭进程池
pool.join()
功能:回收进程池
pool.map(func,iter)
功能: 将要做的时间放入进程池
参数: func 要执行的函数
iter 迭代对象
返回值 : 返回事件函数的返回值列表
进程间通信 (IPC)
原因 : 进程空间相对独立,资源无法相互获取,此时在不同进程间通信需要专门方法。
进程间通信方法 : 管道 消息队列 共享内存 信号
信号量 套接字
管道通信 Pipe
通信原理 : 在内存中开辟管道空间,生成管道操作对象,多个进程使用"同一个"管道对象进行操作即可实现通信
multiprocessing ---》 Pipe
fd1,fd2 = Pipe(duplex = True)
功能 : 创建管道
参数 : 默认表示双向管道
如果设置为False则为单向管道
返回值 : 表示管道的两端
如果是双向管道 都可以读写
如果是单向管道 则fd1只读 fd2只写
fd.recv()
功能 : 从管道读取信息
返回值: 读取到的内容
* 如果管道为空则阻塞
fd.send(data)
功能:向管道写入内容
参数: 要写入的内容
* 可以发送python数据类型
消息队列
队列 : 先进先出
通信原理 : 在内存中建立队列数据结构模型。多个进程都可以通过队列存入内容,取出内容的顺序和存入顺序保持一致
创建队列
q = Queue(maxsize = 0)
功能 : 创建消息队列
参数 : 表示最多存放多少消息。默认表示根据内存分配存 储
返回值 : 队列对象
q.put(data,[block,timeout])
功能: 向队列存储消息
参数 :data 要存的内容
block 默认队列满时会阻塞,设置为False则非阻塞
timeout 超时时间
data = q.get([block,timeout])
功能:获取队列消息
参数:block 默认队列空时会阻塞,设置为False则非阻塞
timeout 超时时间
返回值 : 返回取出的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 判断队列中消息数量
q.close() 关闭队列
共享内存
通信原理:在内存空开辟一块空间,对多个进程可见,进程可以写入输入,但是每次写入的内容会覆盖之前的内容。
obj = Value(ctype,obj)
功能 : 开辟共享内存空间
参数 : ctype 要存储的数据类型
obj 共享内存的初始化数据
返回 :共享内存对象
obj.value 即为共享内存值,对其修改即修改共享内存
obj = Array(ctype,obj)
功能 : 开辟共享内存空间
参数 : ctype 要存储的数据格式
obj 初始化存入的内容 比如列表,字符串
如果是整数则表示开辟空间的个数
返回值 : 返回共享内存对象
* 可以通过遍历过户每个元素的值
e.g. [1,2,3] ---> obj[1] == 2
* 如果存入的是字符串
obj.value 表示字符串的首地址
管道 消息队列 共享内存
开辟空间 内存 内存 内存
读写方式 两端读写 先进先出 覆盖之前内容
双向/单向
效率 一般 一般 较高
应用 多用于父 广泛灵活 需要注意
子进程 进行互斥操作
信号通信
一个进程向另一个进程发送一个信号来传递某种讯息,接受者根据接收到的信号进行相应的行为
kill -l 查看系统信号
kill -sig PID 向一个进程发送信号
关于信号
信号名称 信号含义 默认处理方法
SIGHUP 连接断开
SIGINT CTRU-C
SIGQUIT CTRU-
SIGTSTP CTRL-Z
SIGKILL 终止一个进程
SIGSTOP 暂停一个进程
SIGALRM 时钟信号
SIGCHLD 子进程状态改变时给父进程发出
python 发送信号
signal
os.kill(pid,sig)
功能: 发送信号
参数: pid 目标进程
sig 要发送的信号
import signal
signal.alarm(sec)
功能 : 向自身发送时钟信号 --》 SIGALRM
参数 : sec 时钟时间
* 进程中只能有一个时钟,第二个会覆盖第一个时间
同步执行 : 按照顺序逐句执行,一步完成再做下一步
异步执行 : 在执行过程中利用内核记录延迟发生或者准备 处理的事件。这样不影响应用层的持续执行。 当事件发生时再由内核告知应用层处理
* 信号是唯一的异步通信方法
signal.pause()
功能:阻塞等待接收一个信号
signal.signal(signum,handler)
功能: 处理信号
参数: signum 要处理的信号
handler 信号的处理方法
SIG_DFL 表示使用默认的方法处理
SIG_IGN 表示忽略这个信号
func 传入一个函数表示用指定函数处理
def func(sig,frame)
sig: 捕获到的信号
frame : 信号对象
信号量(信号灯)
原理 : 给定一个数量,对多个进程可见,且多个进程都可以操作。进程通过对数量多少的判断执行各自的行为。
multiprocessing --》 Semaphore()
sem = Semaphore(num)
功能: 创建信号量
参数: 信号量初始值
返回: 信号量对象
sem.get_value() 获取信号量值
sem.acquire() 将信号量减1 当信号量为0会阻塞
sem.release() 将信号量加1
进程的同步互斥
临界资源 :多个进程或者线程都能够操作的共享资源
临界区 : 操作临界资源的代码段
同步 : 同步是一种合作关系,为完成某个任务,多进程或者多线程之间形成一种协调,按照约定或条件执行操作临界资源。
互斥 : 互斥是一种制约关系,当一个进程或者线程使用临界资源时进行上锁处理,当另一个进程使用时会阻塞等待,直到解锁后才能继续使用。
Event 事件
multiprocessing --》 Event
创建事件对象
e = Event()
设置事件阻塞
e.wait([timeout])
事件设置 当事件被设置后e.wait()不再阻塞
e.set()
清除设置 当事件设置被clear后 e.wait又会阻塞
e.clear()
事件状态判断
e.is_set()
Lock 锁
创建对象
lock = Lock()
lock.acquire() 上锁 如果锁已经是上锁状态调用此函数会阻塞
lock.release() 解锁
with lock: 上锁
....
....
解锁
线程
线程也是一种多任务编程方法,可以利用计算机多核资源完成程序的并发执行。线程又被称为轻量级的进程。
线程特征
- 线程计算机多核分配的最小单位
- 一个进程可以包含多个线程
- 线程也是一个运行的过程,消耗计算机资源,多个线程共享进程的资源和空间
- 线程的创建删除消耗的资源都要远远小于进程
- 多个线程之间执行互不干扰
- 线程也有自己的特有属性,比如指令集 ID
threading 模块创建线程
threading.Thread()
功能 : 创建线程对象
参数 :name 线程名称 默认 Thread-1
target 线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
t.start() 启动线程 自动运行线程函数
t.join([timeout]) 回收线程
线程对象属性
t.is_alive() 查看线程状态
t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称
threading.currentThread() 获取当前线程对象
t.daemon 属性
默认情况主线程退出不会影响分支线程执行
如果设置为True 则分支线程随主线程退出
设置方法:
t.daenon = True
t.setDaemon(True)
判断属性值
t.isDaemon()
* 要在start前设置,不会和join同用
创建自己的线程类
步骤:
1.继承Thread
2.加载Thread中的__init__
3.重写run方法
线程通信
通信方法: 多个线程共享进程的空间,所以线程间通 信使用全局变量完成。
注意事项: 线程间使用全局变量往往要同步互斥机制 保证通信安全
线程同步互斥方法
线程的event
e = threading.Event() 创建事件对象
e.wait([timeout]) 如果e为设置状态则不阻塞否则阻塞
e.set() 将e变为设置状态
e.clear() 清除设置
线程锁
lock = threading.Lock() 创建锁对象
lock.acquire() 上锁
lock.release() 解锁
* 也可以通过with上锁,上锁状态调用acquire会阻塞
多线程并发
threading 的多线程并发
对比多进程并发:
* 消耗资源较少
* 线程应该更注意共享资源的操作
* 在python中应该注意GIL问题,网络延迟较高,线程并 发也是一种可行的办法
实现步骤
1. 创建套接字,绑定监听
2. 接收客户端请求,创建新的线程
3. 主线程继续接收其他客户端连接
4. 分支线程启动对应的函数处理客户端请求
5. 当客户端断开,则分支线程结束
cookie
import traceback
traceback.print_exc()
功能 : 更详细的打印异常信息
集成模块的使用
python3 socketserver
功能 : 通过模块的不同类的组合完成多进程/多线程 的 tcp/udp的并发
StreamRequestHandler 处理tcp套接字请求
DatagramRequestHandler 处理udp套接字请求
TCPServer 创建tcp server
UDPServer 创建udp server
ForkingMixIn 创建多进程
ForkingTCPServer --> ForkingMinIn + TCPServer
ForkingUDPServer --> ForkingMinIn + UDPServer
ThreadingMixIn 创建多线程
ThreadingTCPServer --> ThreadingMinIn + TCPServer
ThreadingUDPServer --> ThreadingMinIn + UDPServer
HTTPServer V2.0
- 接收客户端请求
- 解析客户端请求
- 组织数据,形成HTTP response
- 将数据发送给客户端
升级
- 采用多线程并发接收多个客户端请求
- 基本的请求解析,根据请求返回相应的内容
- 除了可以请求静态网页,也可以请求简单的数据
- 将功能封装在一个类中
技术点 :
- socket tcp 套接字
- http协议的请求响应格式
- 线程并发的创建方法
- 类的基本使用
协程基础
定义 : 纤程,微线程。协程的本质是一个单线程程序,所以协程不能够使用计算机多核资源。
作用 : 能够高效的完成并发任务, 占用较少的资源。因 此协程的并发量较高
原理 : 通过记录应用层的上下文栈区,实现在运行中进行上下文跳转,达到可以选择性地运行想要运行的部分,以此提高程序的运行效率。
优点 : 消耗资源少
无需切换开销
无需同步互斥
IO并发性好
缺点 : 无法利用计算机多核
yield —》 协程实现的基本关键字
greenlet
greenlet.greenlet() 生成协程对象
gr.switch() 选择要执行的协程事件
gevent
1. 将协程事件封装为函数
2. 生成协程对象
gevent.spawn(func,argv)
功能 : 生成协程对象
参数 : func 协程函数
argv 给协程函数传参
返回值 : 返回协程对象
3.回收协程
gevent.joinall()
功能 : 回收协程
参数: 列表 将要回收的协程放入列表
gevent.sleep(n)
功能: 设置协程阻塞,让协程跳转
参数: n 阻塞时间
from gevent import monkey
monkey.patch_all()
功能: 修改套接字的IO阻塞行为
* 必须在socket导入之前使用