并发编程—进程
1、进程基础
1.1 什么是进程
狭义定义:进程是正在运行的程序的实例
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
1.2 进程调度
基于单核讨论:
1.2.1 当代操作系统的进程调度方式
时间片轮转+分级反馈队列
1.2.2 先来先服务
a,b程序,若a程序先来,先占用CPU
缺点:程序a先使用,程序b必须等待程序a使用cpu结束后才能使用
1.2.3 短作业优先服务
a,b程序,谁的用时短,先优先调度使用cpu
缺点:若程序a使用时间最长,有N个程序使用时间短,必须等待所有用时短的程序结束后才能使用
1.2.4 时间片轮转
CPU执行的时间1秒中,加载N个程序,要将1秒等分成多N个时间
1.2.5 分级反馈队列
将执行优先分为多层级别
按照优先级先后执行程序
1.3 进程状态
就绪态:所有进程创建时都会进入就绪态,准备调度
运行态:调度后的进程,进入运行态
阻塞态:凡是遇到IO操作的进程,都会进入阻塞态,若IO结束,必须重新进入就绪态
1.4 僵尸进程
指的是子进程已经结束,但PID号还存在,未被销毁
缺点:占用PID号,占用操作系统资源
1.5 孤儿进程
指的是子进程还在执行,但是父进程意外结束
操作系统优化机制:
当孤儿进程结束时,操作系统消除内存并释放PID号
1.6 守护进程
指的是主进程结束后,该主进程产生的所有子进程跟着结束,并被回收,不管子进程到底有没有执行完成
2、进程创建与执行
2.1 创建进程的两种方式
2.1.1 windows与linux/mac下创建进的区别
windows下:创建子进程,windows会将当前父进程代码重新加载执行一次,所以在windows下,实例化进程对象一般放在if __name__ == '__main__':
下,以此来规避掉重用问题
linux/mac下:创建子进程,会将当前父进程代码重新拷贝一份,仅仅是添加了引用
2.1.2 方式1:定义一个任务,实例化进程对象执行任务
import time
from multiprocessing import Process
def task():
print(f'进程开始执行任务')
time.sleep(1)
print(f'进程结束执行任务')
# 在linux/mac下不会报错
# p = Process(target=task)
if __name__ == '__main__':
p = Process(target = task)
p.start()
print(f'已提交进程任务')
2.1.3 方式2:自定义一个进程类
import time
from multiprocessing import Process
class MyProcess(Process):
def run(self):
print(f'进程开始执行任务')
time.sleep(1)
print(f'进程结束执行任务')
if __name__ == '__main__':
p = Process(target = task)
p.start()
print(f'已提交进程任务')
2.2 进程对象的属性
2.2.1 进程号
获取进程号:
1、导入模块from multiprocess import current_process
获取子进程号:current_process().pid
2、cmd中查看进程号
tasklist | findstr 进程名
3、os模块,查看进程号
os.getpid()
获取当前进程号
os.getppid()
获取当前父进程号
回收进程号:
1、使用进程对象的join()
方法
2、主程序正常结束,子进程与主进程也会被回收
2.2.2 p.start()
启动进程,操作系统创建进程
调用该子进程中的p.run()
,执行任务target
2.2.3 p.join()
1、必须在p.start()
后使用
2、作用:使得主程序hold在这里,等待子进程p
执行结束
2.2.4 p.is_alive()
1、判断进程是否存活,如果p仍然运行,返回True
2、返回值为布尔值,存活为真,否则为假
2.2.5 p.terminate()
强制终止进程p
,不会进行任何清理操作,如果p
创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p
还保存了一个锁那么也将不会被释放,进而导致死锁
3、多进程
3.1 什么是多进程
多个进程同时运行,需要注意的是,多进程并发或并行执行,执行顺序与启动顺序无关
实现方法:
import time
from multiprocessing import Process
def f(name):
print('hello', name)
time.sleep(1)
if __name__ == '__main__':
p_lst = []
for i in range(5):
p = Process(target=f, args=('bob',))
p.start()
p_lst.append(p)
3.2 进程池
概念:
定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务
处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务
池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行
好处:
不会增加操作系统的调度难度
节省了开闭进程的时间
一定程度上能够实现并发效果
实现:
模块:from multiprocess import Pool
实例化:pool = Pool([numprocess [,initializer [, initargs]]])
参数:
1. numprocess:要创建的进程数,如果省略,将默认使用`cpu_count()`的值
2. initializer:是每个工作进程启动时要执行的可调用对象,默认为None
3. initargs:是要传给initializer的参数组
使用:
4、进程间通信(IPC)
4.1 进程数据
进程是操作系统最小的资源单位,进程间的数据是相互隔离的
4.2 进程间通信方式
使用第三方库存储数据,进程都可以访问它,间接实现了进程间的通信
如:
1、数据存储在文件中
2、使用进程队列,通过进程队列实现数据交互
3.3 队列
from multiprocessing import Queue
q = Queue(num) # num为整形,代表队列存放的数据最大个数
q.put(value) # 一次存入一个值,若队列满了,程序hold在这里,等待将value存入队列
q.put_nowait() # 一次存入一个值,若队列满了,程序不会hold在这里,直接报错
# 取值原则,先进先出
q.get() # 一次取出一个值,若队列空了,程序hold在这里,等待将队列里有值后取出
q.get_nawait() #一次取出一个值,若队列空了,程序不会hold在这里,直接报错
5、进程互斥锁
见互斥锁