一 介绍
1、什么是进程?
进程是一个资源单位
2、进程与程序:
程序:一对代码文件
进程:执行代码的过程,称之为进程
二 创建,启动和管理进程
Process 类是 multiprocessing 模块的子进程类,用于创建、启动和管理子进程。
Process 和线程模块 treading.Thread 的 API 几乎完全相同。
Process 类用来描述一个进程对象。创建子进程的时候,只需要传入进程函数和函数的参数即可完成 Process 实例化。
Process的属性和方法
方法 | 描述 |
---|---|
start() | 启动进程,会自动调用p对象的run()方法 |
run() | 启动进程时候自动运行的方法,去调用target指向的对象。如果需要自定义进程类,一定要实现run()方法 |
terminate() | 强制终止一个进程,不会做任何清理工作。如果p还有子进程,执行后子进程成为僵尸进程;如果进程有锁,则锁也不会清除,易造成死锁 |
is_alive() | 判断进程是否依然生存,如果是则返回True |
join([timeout]) | 进程的主线程等待进程终止,timeout为可选的超时时间。主线程处于等待,而p处于执行状态。这个方法只适用于start()开启的进程,不能用于run()开启的进程 |
三 同步与异步
同步与异步指的是“提交任务的方式”
同步(串行):
若有两个任务需要提交,在提交第一个任务时,
必须等待该任务执行结束后,才能继续提交并执行第二个任务
两个a,b程序都要提交并执行,假如a先提交执行,b必须等a执行完毕后,才能提交任务。
异步(并发):
若有两个任务需要提交,在提交第一个任务时,
不需要原地等待,立即可以提交并执行第二个任务
两个a,b程序都要提交并执行,假如a先提交并执行,b无需等a执行完毕,就可以直接提交任务。
四 阻塞与非阻塞
阻塞(等待):
凡是遇到IO都会阻塞
`IO`:`input()`,`output()`,`time.sleep(3)`,数据的传输
非阻塞(不等待):
除了IO都是非阻塞,(比如:从1+1开始计算到100万)
五 进程的三种状态
就绪态:
同步与异步(提交任务的方式)
运行态:
程序的执行时间过长 -–-–> 将程序返回给就绪态 非阻塞
阻塞态:
遇到IO
面试题:阻塞与同步是一样的吗?非阻塞与异步是一样的吗?
- 同步与异步:提交任务的方式
- 阻塞与非阻塞:进程的状态
- 异步与非阻塞,CPU的利用率最大化
六 创建进程的两种方式:
方式一:
from multiprocessing import Process
import time
def task(name):
print(f'start{name}---')
time.sleep(3)
print(f'end{name}---')
if __name__ == '__main__':
print("开始执行主进程。。。")
#target=任务(函数地址)--->创建一个子进程
#异步提交了3个任务
obj1 = Process(target=task,args=('letin',))#args内是元组,注意加‘,’
obj2 = Process(target=task,args=('letin',))
obj3 = Process(target=task,args=('letin',))
obj1.start() #.start()告诉操作系统,去创建一个子进程
obj2.start()
obj3.start()
# obj1.join() #告诉主进程,等待子进程结束后,在结束
#主进程是当前程序(程序的执行过程)
结果:
开始执行主进程。。。
startletin---
startletin---
startletin---
endletin---
endletin---
endletin---
方式二:
继承Process方法
from multiprocessing import Process
import time
class MyProcess(Process):
def run(self):
print(f'start...{self.name}的子进程')
time.sleep(3)
print(f'end...{self.name}的子进程')
if __name__ == '__main__':
list1 = []
for line in range(4):
obj = MyProcess()
obj.start()
list1.append(obj)
for obj in list1:
obj.join()
结果:
start...MyProcess-4的子进程
start...MyProcess-2的子进程
start...MyProcess-3的子进程
start...MyProcess-1的子进程
end...MyProcess-4的子进程
end...MyProcess-2的子进程
end...MyProcess-3的子进程
end...MyProcess-1的子进程
主进程
进程调度(了解)
1) 先来先服务调度算法(了解)
- 比如程序 a,b,若a先来,则让a先服务,待a服务完毕后,b再服务。
- 缺点: 执行效率低。
2) 短作业优先调度算法(了解)
- 执行时间越短,则先先调度。
缺点:
导致执行时间长的程序,需要等待所有时间短的程序执行完毕后,才能执行。
现代操作系统的进程调度算法: 时间片轮转法 + 多级反馈队列 (知道)
3) 时间片轮转法
- 比如同时有10个程序需要执行,操作系统会给你10秒,然后时间片轮转法会将10秒分成10等分。
4) 多级反馈队列:
1级队列: 优先级最高,先执行次队列中程序。
2级队列: 优先级以此类推
3级队列:
七 僵尸进程与孤儿进程
僵尸进程(有坏处)
在子进程结束后,主进程没有正常结束, 子进程PID不会被回收
缺点:
操作系统中的PID号是有限的,如有子进程PID号无法正常回收,则会占用PID号。
资源浪费。
若PID号满了,则无法创建新的进程。
孤儿进程(没有坏处)
在子进程没有结束时,主进程没有“正常结束”, 子进程PID不会被回收
操作系统优化机制(孤儿院):
当主进程意外终止,操作系统会检测是否有正在运行的子进程,会他们放入孤儿院中,让操作系统帮你自动回收。
八 守护进程
守护进程会在主进程代码执行结束后就终止
from multiprocessing import Process
# 在子进程中调用,可以拿到子进程对象.pid可以pid号
# 在主进程中调用,可以拿到主进程对象.pid可以pid号
import time
def demo(name):
print(f'start....{name}')
time.sleep(1000)
print(f'end......{name}')
print('子进程结束啦啊....~~~')
if __name__ == '__main__':
p = Process(target=demo, args=('童子军jason1号', ))
# 守护进程必须在p.start()调用之前设置
p.daemon = True # 将子进程p设置为守护进程
# 告诉操作系统帮你开启子进程
p.start()
# p.join()
time.sleep(1)
print('皇帝驾崩啦啊~~~')
九 进程间数据隔离
进程隔离是为保护操作系统中进程互不干扰
from multiprocessing import Process
n=100
def work():
global n
n=0
print('子进程内: ',n)
if __name__ == '__main__':
p=Process(target=work)
p.start()
print('主进程内: ',n)
结果:
主进程内: 100
子进程内: 0
十 进程互斥锁
进程同步(multiprocess.Lock
)
锁-–multiprocess.Lock
多进程模拟抢票实例
#文件db的内容为:{"count":1}
#注意一定要用双引号,不然json无法识别
from multiprocessing import Process,Lock
import time,json,random
def search():
dic=json.load(open('db'))
print('\033[43m剩余票数%s\033[0m' %dic['count'])
def get():
dic=json.load(open('db'))
time.sleep(0.1) #模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1
time.sleep(0.2) #模拟写数据的网络延迟
json.dump(dic,open('db','w'))
print('\033[43m购票成功\033[0m')
def task():
search()
get()
if __name__ == '__main__':
for i in range(100): #模拟并发100个客户端抢票
p=Process(target=task)
p.start()
# 引发问题:数据写入错乱
互斥锁保证数据安全
from multiprocessing import Process,Lock
import time,json,random
def search():
dic=json.load(open('db'))
print('\033[43m剩余票数%s\033[0m' %dic['count'])
def get():
dic=json.load(open('db'))
time.sleep(random.random()) # 模拟读数据的网络延迟
if dic['count'] >0:
dic['count']-=1
time.sleep(random.random()) # 模拟写数据的网络延迟
json.dump(dic,open('db','w'))
print('\033[32m购票成功\033[0m')
else:
print('\033[31m购票失败\033[0m')
def task(lock):
search()
lock.acquire() # 将买票这一环节由并发变成了串行,牺牲了运行效率但是保证了数据的安全
get()
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(100): # 模拟并发100个客户端抢票
p=Process(target=task,args=(lock,))
p.start()
总结:加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
问题:虽然可以用文件共享数据显示进程间数据通信但问题是
- 效率低(共享数据基于文件,而文件是硬盘上的数据)
- 需要自己加锁处理
针对上述问题,我们需要找到一种更加合理快捷的方式,那就是队列和管道
十一 进程间通信(IPC机制)
我们知道进程之间数据是相互隔离的,要想实现进程间的通信(IPC机制),就必须借助于一些技术才可以,比如multiprocessing模块中的:队列和管道,这两种方式都是可以实现进程间数据传输的,由于队列是管道+锁的方式实现,所以我们着重研究队列即可
十二 队列
概念介绍
创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
大白话总结一下就是队列支持多个人从队列的一端放入数据,同样支持多个人从队列的另一端取数据
三种队列:
from multiprocessing import Queue # multiprocessing提供队列 先进先出
from multiprocessing import JoinableQueue # 基于 Queue 封装的队列 先进先出
import queue # python内置的队列 先进先出
第一种:
# Queue(5)指的是队列中只能存放5份数据
q_obj1 = Queue(5) # q_obj1队列对象
# # 添加数据到队列中
q_obj1.put('jason')
print('添加1个')
q_obj1.put('hcy')
print('添加1个')
# put: 只要队列满了,会进入阻塞
# q_obj1.put('sean')
# put_nowait: 只要队列满了,就会报错
# q_obj1.put_nowait('sean')
# # get: 只要队列中有数据,就能获取数据,若没有则会进入阻塞
# print(q_obj1.get())
# print(q_obj1.get())
# get_nowait: 若队列中没有数据获取则会报错
# print(q_obj1.get_nowait())
第二种:
q_obj1 = JoinableQueue(5) # q_obj1队列对象
q_obj1.put('jason')
print('添加1个')
q_obj1.put('hcy')
print('添加1个')
# put_nowait: 只要队列满了,就会报错
# q_obj1.put_nowait('sean')
# get: 只要队列中有数据,就能获取数据,若没有则会进入阻塞
print(q_obj1.get())
print(q_obj1.get())
第三种:
# q_obj1 = queue.Queue(5) # q_obj1队列对象
# # 添加数据到队列中
# q_obj1.put('jason')
# print('添加1个')
# q_obj1.put('hcy')
# print('添加1个')
# put: 只要队列满了,会进入阻塞
# q_obj1.put('sean')
# put_nowait: 只要队列满了,就会报错
# q_obj1.put_nowait('sean')
# get: 只要队列中有数据,就能获取数据,若没有则会进入阻塞
# print(q_obj1.get())
# print(q_obj1.get())
十三生产者消费者模型
在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据(做包子的)之后不用等待消费者(吃包子的)处理,直接扔给阻塞队列(盘子),消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式
基于队列实现生产者消费者模型
from multiprocessing import Process,Queue
import time,random,os
def consumer(q):
while True:
res=q.get()
time.sleep(random.randint(1,3))
print('%s 吃 %s' %(os.getpid(),res))
def producer(q):
for i in range(10):
time.sleep(random.randint(1,3))
res='包子%s' %i
q.put(res)
print('%s 生产了 %s' %(os.getpid(),res))
if __name__ == '__main__':
q=Queue()
#生产者们:即厨师们
p1=Process(target=producer,args=(q,))
#消费者们:即吃货们
c1=Process(target=consumer,args=(q,))
#开始
p1.start()
c1.start()
print('主')