多进程
一、多任务原理
-
概念
现代操作系统比如Mac OS X,UNIX,Linux,Windows等,都是支持“多任务”的操作系统
-
什么叫多任务?
就是操作系统可以同时运行多个任务
-
单核CPU实现多任务原理
操作系统轮流让各个任务交替执行,QQ执行2us(微秒),切换到微信,在执行2us,再切换到陌陌,执行2us……。表面是看,每个任务反复执行下去,但是CPU调度执行速度太快了,导致我们感觉就像所有任务都在同时执行一样
- 多核CPU实现多任务原理
真正的秉性执行多任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行
-
并发与并行
-
并发
CPU调度执行速度太快了,看上去一起执行,任务数多于CPU核心数
-
并行
真正一起执行,任务数小于等于CPU核心数
-
并发是逻辑上的同时发生,并行更多是侧重于物理上的同时发生。
-
-
实现多任务的方式
-
多进程模式
启动多个进程,每个进程虽然只有一个线程,但是多个进程可以一起执行多个任务
-
多线程模式
启动一个进程,在一个进程的内部启动多个线程,这样多个线程也可以一起执行多个任务
-
多进程+多线程
启动多个进程,每个进程再启动多个线程
-
协程
-
多进程+协程
-
二、进程
1、概念
-
什么是进程?
是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
-
对于操作系统
一个任务就是一个进程。比方说打开浏览器就是启动一个浏览器的进程,在打开一个记事本就启动一个记事本进程,如果打开两个记事本就启动两个记事本进程
2、使用进程
-
单进程现象
需要等待代码执行完后再执行下一段代码
import time
def run1():
while 1:
print("lucky is a good man")
time.sleep(1)
def run2():
while 1:
print("lucky is a nice man")
time.sleep(1)
if __name__ == "__main__":
run1()
# 不会执行run2()函数,只有上面的run1()结束才能执行run2()
run2()
-
启动进程实现多任务
-
multiprocessing模块
跨平台的多进程模块,提供了一个Process类用来实例化一个进程对象
-
Process类
作用:创建进程(子进程)
-
__name__
-
(说明:这个知识点跟进程没关系。)
my_test.py
print(__name__)
if __name__ == '__main__':
print('我是my_test.py')
main.py
import my_test
当执行 my_test.py 时,结果是:
__main__
我是my_test.py
当执行 main.py 时,结果是:
my_test
-
- Windows 上多进程的实现问题:在 Windows 上,子进程会自动 import 启动它的这个文件,而在 import 的时候是会执行这些语句的。如果这样话就会无限递归创建子进程报错。所以必须把创建子进程的部分用那个 if 判断保护起来,import 的时候
__name__不是__main__,就不会递归运行了。所以Windows系统一定要写__name__="__main__"。
- Windows 上多进程的实现问题:在 Windows 上,子进程会自动 import 启动它的这个文件,而在 import 的时候是会执行这些语句的。如果这样话就会无限递归创建子进程报错。所以必须把创建子进程的部分用那个 if 判断保护起来,import 的时候
-
-
启动进程实现多任务
from multiprocessing import Process
创建子进程
P = Process(target=run,args=("nice",),name='当前进程名称')
-
target指定 子进程运行的函数
-
args 指定传递的参数 , 是元组类型
-
启动进程:Process对象.start()
-
参数 说明 target 指定进程执行的任务 args 给进程函数传递的参数,是一个元组
获取进程信息
- os.getpid() 获取当前进程id号
- os.getppid() 获取当前进程的父进程id号
- multiprocessing.current_process().name 获取当前进程名称
父子进程的先后顺序
-
默认 父进程的结束不能影响子进程 让父进程等待子进程结束再执行父进程
-
p.join() 阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。
-
全局变量在过个进程中不能共享
注意: 在子线程中修改全局变量时对父进程中的全局变量没有影响
-
-
示例代码
-
-
-
-
主进程负责调度
主进程主要做的是调度相关的工作,一般不负责具体业务逻辑
-
父子进程的先后顺序
主进程的结束不能影响子进程,所以可以等待子进程的结束再结束主进程,等待子进程结束,才能继续运行主进程
p.join() 阻塞当前进程,直到调用join方法的那个进程执行完,再继续执行当前进程。
-
-
3、全局变量在多个子进程中不能共享
原因:
在创建子进程时对全局变量做了一个备份,父进程中num变量与子线程中的num不是一个变量
尝试列表是否能共享
4、启动大量子进程
-
-
-
获取CPU核心数
print('CPU number:' + str(multiprocessing.cpu_count()))
-
导入
from multiprocesssing import Pool
-
开启并发数
pp = Pool([参数]) #开启并发数 默认是你的核心数
-
创建子进程,并放入进程池管理
apply_async为非阻塞模式(并发执行)
pp.apply_async(run,args=(i,)) #args参数 可以为元组 或者是列表[]
-
关闭进程池
pp.close()关闭进程池
-
join()
在调用join之前必须先调用close,调用close之后就不能再继续添加新的进程了
pp.join()
进程池对象调用join,会等待进程池中所有的子进程结束完毕再去执行父进程
-
实例
get方法:获取进程的返回值
注意:这样来获取每个进程的返回值 那么就会变成单进程
-
-
5、map方法
-
-
-
概述
如果你现在有一堆数据要处理,每一项都需要经过一个方法来处理,那么map非常适合
比如现在你有一个数组,包含了所有的URL,而现在已经有了一个方法用来抓取每个URL内容并解析,那么可以直接在map的第一个参数传入方法名,第二个参数传入URL数组。
-
概述
在这里初始化一个Pool,指定进程数为3,如果不指定,那么会自动根据CPU内核来分配进程数。
然后有一个链接列表,map函数可以遍历每个URL,然后对其分别执行scrape方法。
-
-
6、单进程与多进程复制文件对比
-
-
-
单进程复制文件
-
多进程复制文件
-
-
7、进程间通信
-
-
-
队列共享
-
导入
from multiprocessing import Queue
-
使用
que = Queue() #创建队列
que.put(数据) #压入数据
que.get() #获取数据
-
队列常用函数
Queue.empty() 如果队列为空,返回True, 反之False
Queue.full() 如果队列满了,返回True,反之False
Queue.get([block[, timeout]]) 获取队列,timeout等待时间
Queue.get_nowait() 相当Queue.get(False)
Queue.put(item) 阻塞式写入队列,timeout等待时间
Queue.put_nowait(item) 相当Queue.put(item, False)
-
特点:先进先出
-
注意:
get方法有两个参数,blocked和timeout,意思为阻塞和超时时间。默认blocked是true,即阻塞式。
当一个队列为空的时候如果再用get取则会阻塞,所以这时候就需要把blocked设置为false,即非阻塞式,实际上它就会调用get_nowait()方法,此时还需要设置一个超时时间,在这么长的时间内还没有取到队列元素,那就抛出Queue.Empty异常。
当一个队列为满的时候如果再用put放则会阻塞,所以这时候就需要把blocked设置为false,即非阻塞式,实际上它就会调用put_nowait()方法,此时还需要设置一个超时时间,在这么长的时间内还没有放进去元素,那就抛出Queue.Full异常。
另外队列中常用的方法
-
队列的大小
Queue.qsize() 返回队列的大小 ,不过在 Mac OS 上没法运行。
实例
-
-
字典共享
-
导入
import multiprocess
-
概述
Manager是一个进程间高级通信的方法 支持Python的字典和列表的数据类型
-
创建字典
myDict = multiprocess.Manager().dict()
实例
-
-
列表共享
-
导入
import multiprocess
-
创建列表
myDict = multiprocess.Manager().list()
实例(字典与列表共享)
-
-
注意
进程名.terminate() 强行终止子进程
-
deamon
在这里介绍一个属性,叫做deamon。每个进程程都可以单独设置它的属性,如果设置为True,当父进程结束后,子进程会自动被终止。
进程.daemon = True
设置在start()方法之前
-
进程名.terminate() 强行终止子进程
-
-
8、进程实现生产者消费者
生产者消费者模型描述:
生产者是指生产数据的任务,消费者是指消费数据的任务。
当生产者的生产能力远大于消费者的消费能力,生产者就需要等消费者消费完才能继续生产新的数据,同理,如果消费者的消费能力远大于生产者的生产能力,消费者就需要等生产者生产完数据才能继续消费,这种等待会造成效率的低下,为了解决这种问题就引入了生产者消费者模型。
生产者/消费者问题可以描述为:两个或者更多的进程(线程)共享同一个缓冲区,其中一个或多个进程(线程)作为“生产者”会不断地向缓冲区中添加数据,另一个或者多个进程(线程)作为“消费者”从缓冲区中取走数据。
-
-
-
代码
-
-
9、案例(抓取斗图)
浙公网安备 33010602011771号