多任务编程——多进程
本文主讲:Python中有关于多进程相关的知识…..
进程以及状态【重点】
一:什么是进程?
概念:进程(Process)是系统资源分配的基本单位,也是线程的容器,即线程是轻量级的进程,一个进程包含多个线程
程序与进程的区别:
程序:例如xxxx.py就是程序,是静态的
进程:进程是程序运行起来后,程序代码+用到的资源,它是动态的,是操作系统分配资源的基本单位,即操作系统先将资源分配给进程,进程在分配给其他线程等
注意事项:如果没有进程线程是不能独立运行的
截图说明
进程的几种状态
读前须知:cpu处理多任务的方式为时间片轮询!
新建状态:cpu在处理一个任务时,另一个新的任务加入进来,加入的任务为进程新建状态,或者任务没有获取到资源的状态
就绪状态:两种可能是就绪状态,第一种刚加入的任务获取到资源进入就绪状态,等待运行,第二种是任务刚从运行状态结束等待下一次运行为就绪状态
运行状态:cpu通过时间片轮询方式轮询到这个任务时为运行状态
等待状态:在cpu处理一个任务时,这个任务意外关闭(time.sleep(2))(阻塞),则这个任务进入等待状态,满足条件后进入就绪状态
死亡状态:一个任务运行结束后,没有其他的任务即机死亡状态
流程演示:
流程图解:
进程——基本使用【重点】
创建子进程
语法:
1:导入模块 import multiprocessing
2:创建进程对象 process_obj = multiprocessing.Process(target = 函数任务名无括号)
3:启动进程 process_obj . start( )
创建的子进程对象的参数作用:
target:为子进程传递任务函数
args:给任务函数传参(元组形式)
kwargs:给认为函数传参(字典形式)
name:为创建的子进程命名,也可以不命名
group:为创建的子进程指定组,大多数用不到
Process类创建的子进程方法及功能:
创建的子进程对象.start():启动子进程
创建的子进程对象.is_alive():判断子进程是否还活着
创建的子进程对象.join():是否等待其他子进程执行完毕再执行
创建的子进程对象.terminate():不管任务是否完成立刻结束子进程
Process类创建的子进程属性:pid:当前进程的pid号(进程号)
注意事项:
1:进程与线程是受操作系统调配的,因此不同的操作系统执行的结果可能会不同
2:程序启动会建立一个主进程,主进程内又包含了主线程,因此一个程序启动就会建立一个主线程
3:target方法指定的函数任务没有括号!
4:创建子进程的方法和创建子线程的方法类似
快速代码体验
截图感悟:从代码打印结果上来看,主线程和子进程是同时运行的!
进程——获取名称、PID号、PPID号【重点】
获取当前进程名称——multiprocessing.current_process( )
功能:获取当前运行的进程名称(可以是主进程也可以是子进程)
语法:print(multiprocessing.current_process( ))
注意事项:获取进程名称是主进程还是子进程要看代码书写的位置
快速代码体验
获取进程id的几种方法
你问我答:什么是进程id?为什么要获取进程id?
答:进程id就是进程的编号,获取进程id是为了在终端输入特定指令(kill -9)杀死该进程
方法一:multiprocess.current_process( ).pid 即可获取当前线程的id号,pid是 process id 的缩写,是进程id的意思
快速代码体验
方法二:os 模块下的 getpid()方法获取进程的编号(id)即 os . getpid()
注意事项:os . getpid()写在什么位置就获取的是什么位置的进程id号
快速代码体验
获取进程 父id 的方法
你问我答:什么是进程的 父id
答:进程的 父id 就是创建这个进程的那个进程的id号就是父id
进程父id获取方法:os 模块下的 getppid()方法 ,即 os . getppid( )
注意事项:os . getppid()写在什么位置就获取的是什么位置的进程父id号
快速代码体验
进程——终端指令杀死进程【重点】
目标:掌握在终端输入指令 kill -9 进程编号 作用
终端输入指令 kill -9 进程编号 以及 kill -15 进程编号 的作用
Kill -9 进程id:在终端输入这个指令,不管这个进程id的进程是否结束,直接杀死该进程(结束掉)
注意事项:
1:如果用 kill -9 指令杀死的是主进程,那么其余的子进程也随之结束
2:如果用 kill -9 指令杀死的是子进程,那么主进程不受影响,继续执行,直到结束!
快速代码体验
进程——参数、全局变量【重点】
验证子进程之间不能共享一个全局变量
验证思想:定义一个函数work1,对一个全局变量进行累加处理,在定义一个work2函数读取这个全局变量,再定义两个子进程,两个子进程分别执行这两个函数任务,如果执行读取全局变量的进程可以成功读取到另一个进程修改后的全局变量,那么说明多个进程间可以共享全局变量,如果执行读取全局变量的进程读取到的全局变量是没有修改的全局变量,说明多个进程之间不可以共享全局变量!!!
为什么子进程间不能共享全局变量?
答:因为子进程只是把全局变量的值都复制到自己内部然后独立修改,不会修改全局变量的值,因此多个子进程间互不影响
子进程间不能共享全局变量的例子:计算机打开微信(子进程1)与浏览器(子进程2)如果子进程间可以共享全局变量的话,那么在微信上的聊天记录不就被浏览器获取了嘛!
程序设计截图
子进程访问(修改、读取)全局变量的底层原理
原理图示:
注意事项:子进程访问(修改)全局变量时并不会真正改变全局变量的值,而是将全局变量的值复制下来供自己使用,并不会真正的改变全局变量的值!
给子进程指定的任务函数传递参数
传递方法:
1:args(元组法)
2:kwargs(字典法)
3:args、kwargs(元组字典法)
快速代码体验
进程——设置守护主线程【重点】
设置子进程守护主线程
功能:将子进程设置为守护主进程,即主进程结束,子进程也随之结束
实现代码:子进程对象 . daemon = True
注意事项:设置子进程守护主进程要在子进程开始运行(start())之前设置
快速代码体验
进程——强制杀死子进程【重点】
强制杀死子进程
功能:不管子进程是否结束,强制杀死子进程
语法:创建的子进程对象 . terminate()
注意事项:强制杀死子进程与设置子进程守护主进程不一样
快速代码体验
多进程与多线程的区别【面试】【重中之重】
掌握进程与线程之间的区别
进程:可以完成多任务,一个进程就相当于计算机中一个应用程序,多进程就相当于在计算机中打开多个qq应用程序
线程:可以完成多任务,一个线程就相当于计算机中一个应用中的一个功能窗口,多线程相当于一个qq应用程序打开多个聊天窗口
实例截图
使用区别:
1:进程是系统进行资源分配的进本单元,线程是cpu调度和分派的基本单位,进程包括线程
2:线程占用内存资源比进程少(划分尺度小),这使的多线程程序有高并发行
3:多个进程运行时,每个进程都拥有独立的内存资源(多进程间不能共享全局变量),而多个线程运行时,共享一个内存(因此多线程间可共享全局变量),因此多进程相对于多线程更稳定
图解:
进程与线程对比图示
注意事项:
1:一个程序至少有一个进程(主进程),一个进程至少有一个线程(主线程)
2:线程不能单独运行,必须依赖进程中运行(进程提供线程运行的资源),比如必须开启qq(进程)才能聊天(线程)
实际处理问题时进程与线程的优先选择:
1:需要频繁的创建销毁,优先选择线程,因为进程占用资源多,创建销毁慢,例如WEB服务器
2:线程切换速度快,在需要大量计算,频繁切换时,使用线程,例如算法、图像处理
3:多机分布用进程、多核分布用线程
4:需要运行稳定优先用进程,想要运行速度快优先用线程
5:cpu密集型(频繁操作),优先使用进程,因为现在python的解释器cpython存在GIL(全局解释器锁)创建互斥锁,会造成线程处理问题时,多核cpu处理问题变为单核cpu,进程不会出现这种现象
6:i/o密集型可以使用线程
注意事项:
1:在以后实际处理问题时,要用进程+线程+协程的思想,不要选入非此即彼的思想中,灵活运用
消息队列——基本操作【重点】
为什么要学习消息队列?什么是队列?
答:进程之间是不可以进行共享全局变量的,但是我们可以创建一个消息队列,用来传递进程间的数据,从而间接进行数据共享,消息队列相当于两个进程间的对接人,媒婆,进程靠着消息队列传递消息
什么是队列:队列是一种线性数据结构类型,队列存放数据在队尾存放,而取数据在队首取走
图示
python中怎么创建消息队列:multiprocessing模块中的Queue(num)类可创建消息队列
例如:queue1 = multiprocessing.Queue(3) 代表创建一个长度为3的队列
Queue(队列)的 put()以及 put_nowait( ) 与 get()和 get_nowait( ) 的基本作用
功能:
put(“3”): 向队列中存放“3”数据(从队尾存入),如果队列数据已满,那么会等待队列释放后再存入,程序处于阻塞状态
put_nowait( “3”): 向队列中存放“3”数据,如果队列已满,程序不会进入阻塞状态,直接报错
get():在队列中取出数据(从队首取出),执行一次在队列中取走一个数据!如果队列已空,程序进入阻塞状态,等待数据存入队列中在取出
get_nowait( ) :在队列中取出数据(从队首取出),执行一次在队列中取走一个数据!如果队列已空,程序不会进入阻塞状态,直接报错
快速代码体验(put方法)
put_nowait()方法
get()方法
get_nowait( )方法
消息队列—常见判断【重点】
队列中 full()方法与 empty()方法的作用
消息队列的full()方法:判断消息队列是否为满,如果消息队列已满返回True,反之False
实例:bool = queue.full() 判断queue这个消息队列是否已满,并且返回值
快速代码体验
消息队列的empty()方法:判断消息队列是否为空,为空返回True,反之False
实例:bool = queue.empty() 判断queue这个队列是否为空,并且返回值
快速代码体验
消息队列中qsize()方法的作用
功能:返回消息队列中尚存的数据个数,注意是尚存,不是消息队列的长度!
语法:num = 消息队列对象 . qsize()
快速代码体验
消息队列——判断是否为空的坑【重点】
判断消息队列是否为空的坑!!
你问我答:是什么坑?
答:当消息队列被数据项填满,用empty()判断消息队列是否为空时,返回值为True,说明消息队列已空,这就是坑
二:出现这种坑的原因
答:可以理解为向队列中存数据为子进程1控制,判断队列是否为空是子进程2控制,但是子进程2的运行速度快过于子进程1,造成了数据还没有存入队列,就进行了判断,因此出现了队列中明明有数据却判断为空的原因
原理截图:
代码图示(坑)
解决这个坑的方法代码截图
Queue实现进程间的通信【重点】
消息队列可以实现进程间的通信与通信原理
通信原理:Queue(消息队列)相当于一个中介,它可以存储进程1的数据,从而让进程2取出,这样就实现了进程间数据的传递
原理模型截图:
代码截图:
进程——进程池【重点】
什么是进程池?
进程池概念:当创建大量进程的时候,multiprocess的Process()方法就太过麻烦,我们需要采用multiprocess内的Pool()来创建进程池,用来保存大量进程,即进程池是进程的容器,我们可以用它创建指定数量的进程
进程序功能:管理与维护进程池
创建进程池语法:pool_obj = multiprocess.Pool( 3 ) 表示最大创建一个内含三个进程的进程池
注意事项:multiprocess.Pool( 3 )表示最大可以创建三个进程,在只有一个任务的时候,只需创建一个进程完成任务即可
不需要把三个进程都创建出来完成任务
图示:
进程池工作方式(同步与异步):
同步工作方式
概念:进程池内的进程执行函数任务时,同一时间只允许一个进程工作
函数方法:进程池对象 . apply(函数任务,(函数参数1,参数2…….)):以同步的方式执行任务
注意事项:同步的方法给任务函数传递参数只有以元组的方式进行传递
图示:
快速代码体验
异步工作方式:
概念:进程池内的进程同一时间可以一起工作
函数方法:进程池对象 . apply_async(函数参数1,参数2….)
注意事项:
1:异步的方法给任务函数传递参数只有以元组的方式进行传递
2:在使用进程池异步工作方式时,一定要将添加完任务的线程池执行close()操作,表示不再接受新的任务,否则的话会造成进程池不运行!
3:采用进程池异步工作方式,主进程将不再等待进程池执行结束后退出!!为了避免数据在主进程退出时销毁,要对进程池采用 join()方法!
图示:
快速代码体验
进程——进程池间的通信
进程池内进程间的通信
通信原理:利用进程池内创建的Queue(队列)来实现进程池内进程间的通信
进程池内创建队列方法:pool_queue = multiprocessing.Manager().Queue(3) 【创建一个长度为3的线程池队列】
注意事项:
1:在进程池中创建队列,一定要用multiprocessing模块内的Manager()类里面的Queue()类来创建!
2:在采用异步方法实现进程池中进程的通信时,不要在最后忘记close()方法和join()方法
通信图示
快速代码体验(同步):
异步【重点】:
案例:多进程文件夹copy器
怎么创建文件夹?
os.mkdir(“文件夹名称”)
怎么获取文件夹内所有文件名并且保存至一个列表内?(包括文件名和文件夹名)
os.listdir(“目标文件夹名称”)
案例:多进程简单web服务器
略…..
未完待续…..