一、多任务编程
利用计算机多核特点,同时执行多个任务。通过充分利用计算机资源,来提高程序的运行效率。
实现:多进程 多线程
【1】什么叫“多任务”呢?
就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运 ⾏着,只是桌⾯上没有显示⽽已。
【2】单核CPU如何实现“多任务”呢?
操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。
【3】多核CPU如何实现“多任务”呢?
真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核 ⼼上执⾏。
【4】并行 &&并发。
并行:多个计算机核心同时处理多个任务;多个任务之间是并行关系
并发:计算机同时处理多个任务,内核在多个任务之间不断切换,达到好像在同时处理的运行效果。此时多个任务实际为并发关系,如IO多路复用
二、多进程编程
【1】进程 VS 程序
编写完毕的代码,在没有运⾏的时候,称之为程序
正在运⾏着的代码,就成为进程
注意: 进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的
进程:程序在计算机中运行一次的过程,是一个动态的过程描述;占有CPU内存等计算机资源,具有一定的生命周期
程序:静态的可执行文件,占有磁盘,不占有计算机的运行资源;磁盘不属于计算机资源
备注:同一程序的不同执行过程,分配的计算机资源不同,属于不同的进程
【2】进程的创建流程
1、用户空间运行一个程序,发起进程创建
2、操作系统接受用户的请求,开启进程的创建
3、操作系统分配系统资源,确定进程状态
4、将创建好的进程提供给应用层使用
【3】进程的相关概念
1、cpu时间片
如果一个进程占有计算机核心,我们称之为该进程在CPU的时间片上。
多个任务实际上对CPU会进行争夺,一般由操作系统分配CPU时间片
2、进程控制块
PCB(Processing Control Block)
在操作系统中,进程创建后会自动产生一个空间存放进程信息,称之为进程控制块。
进程控制块包含进程的PID,memory 进程占有的内存位置,创建时间,用户,状态等
3、进程PID
进程在操作系统中的唯一编号,是一个大于0的整数,由系统自动分配
4、进程的特征
进程是操作系统分配计算机资源的最小单位
每个进程都有自己单独的虚拟内存空间,进程间的执行相互独立,互不影响
5、ps -aux 查看进程的信息
D 等待态 不可中断等待
S 等待态 可中断等待
T 等待态 暂停状态
R 运行态 就绪态,运行态
Z 僵尸态 进程已经结束,但仍存在一些问题解决
+ 前台进程
< 高优先级
N 低优先级
l 有进程链接
s 会话组组长
6、进程的优先级
优先级决定了一个进程的执行权限和占有资源的优先程度
top:动态的查看进程的优先级
-20 —19,其中-20的优先级最高
nice:指定优先级运行程序
7、父子进程 :
除了初始化进程每个进程都有一个父进程,也有可能有0个或多个进程,
pstree: 查看进程树
ps -ajx :查看父进程的PID
【4】进程的状态
1、三态:
就绪:进程具备执行条件,等待系统分配CPU
执行:进程占有的CPU时间片,处于运行状态
等待:进程暂时不具备运行条件,需要阻塞等待
2、五态(增加新建态和终止态)
新建:创建一个新的进程,获取资源的过程
终止:进程结束,释放资源的过程
【5】进程的相关函数
1、os.getpid()
功能:获取当前进程的PID
返回值:返回PID值
2、os.getppid()
功能:获取父进程的pid号
返回值:返回PID号
3、os._exit(status)
功能:退出进程
返回值:使用整数标识进程的退出状态,并通过人为赋予相应的含义
4、sys.exit([status])
功能:退出状态
返回值:默认为0,如果是整数,则表示退出状态;如果是字符串,则表示在退出时打印内容。
备注:sys.exit()可以通过捕获SystemExit异常阻止退出
【6】孤儿进程
父进程先于子进程退出,此时子进程就会成为孤儿进程。
孤儿进程会被系统指定的进程收养,即系统进程会成为该孤儿进程新的父进程,处理孤儿进程的退出状态;
孤儿进程一定不会成为僵尸进程。
【7】僵尸进程
子进程先于父进程退出,并且父进程没有处理子进程退出状态,此时子进程成为僵尸进程;
僵尸进程已经结束,但是会滞留部分PCB信息在内存,大量的僵尸会消耗系统资源,应该尽量避免僵尸进程的产生。
三、创建子进程
【1】os.fork()
Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程 序中轻松创建⼦进程:
Linux 操作系统提供了一个 fork() 函数用来创建子进程,这个函数很特殊,调用一次,返回两次,
因为操作系统是将当前的进程(父进程)复制了一份(子进程),然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的 PID。
我们可以通过判断返回值是不是 0 来判断当前是在父进程还是子进程中执行。
执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中
普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次
⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号
import os
import time
# 定义一个全局变量money
money = 100
print("当前进程的pid:", os.getpid())
print("当前进程的父进程pid:", os.getppid())
time.sleep(15)
p = os.fork()
# 子进程返回的是0
if p == 0:
money = 200
print("子进程返回的信息, money=%d" %(money))
# 父进程返回的是子进程的pid
else:
print("创建子进程%s, 父进程是%d" %(p, os.getppid()))
print(money)
【2】多进程修改全局变量
多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,互 不影响
【3】如何避免僵尸进程的产生
(1)父进程先退出
1、父进程创建子进程等待子进程退出
2、子进程创建二级子进程,然后立马退出
3、二级子进程成为孤儿,处理具体事宜
(2)父进程处理子进程的状态
pid , status =os.wait()
功能:在父进程中阻塞等待处理子进程的退出
返回值:pid退出的子进程PID号
status 子进程的退出状态默认为0
pid,status = os.waitpid(pid,option)
功能,返回值:同wait
参数:
pid -1 表示任意子进程退出
>0 指定PID号的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞
waitpid(-1,0) 等价于wait()
四、multiprocessing模块
Windows没有fork调⽤,由于Python是跨平台的, multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。
【0】使用multiprocessing模块
1、需要将要做的事情封装为函数
2、使用multiprocessing中提供的Process类创建进程对象
3、通过进程对象和process初始化函数,对进程进行设定,并且绑定要执行的事件
4、启动进程,会自动执行相关联的函数
5、事件完成后回收进程
【1】创建进程对象
Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调⽤对象;
args:表示调⽤对象的位置参数元组;
kwargs:表示调⽤对象的关键字参数字典;
name:为当前进程实例的别名;
group:⼤多数情况下⽤不到;
P.start()
启动进程,此时进程被创建,并自动执行进程函数
P.join([timeout])
功能:阻塞等待回收响应的进程
参数 :超时时间
【2】Process类常⽤⽅法:
is_alive(): 判断进程实例是否还在执⾏;
join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
start(): 启动进程实例(创建⼦进程);
run(): 如果没有给定target参数,对这个对象调⽤start()⽅法时,
就将执 ⾏对象中的run()⽅法;
terminate(): 不管任务是否完成,⽴即终⽌;
【3】Process类常⽤属性:
name:当前进程实例别名,默认Process-N,N为从1开始计数;
pid:当前进程实例的PID值;
p.daemon 默认值为False,父进程退出,不会影响子进程的运行
设置为True时,父进程退出,子进程也将退出
daemon 的设置必须在start前
如果设置daemon 为True,则不再使用join,在子进程退出前,会自动做处理
p.is_alive() 判断进程生命周期状态,返回True/False
【4】备注
1、multiprocessing创建进程是原来进程的子进程,创建后父子进程各自执行互不影响
2、子进程同样是复制父进程的空间,子进程对内容的修改不会影响到父进程的空间
3、join回收子进程,会有效的阻止僵尸进程的产生
4、通常使用multiprocessing创建进程,父进程只用作进程的创建和回收,不做其他工作
5、join()方法可以等待子进程结束后再继续往下运行(更准确地说,在当前位置阻塞主进程,带执行join()的进程结束后再继续执行主进程),通常用于进程间的同步。
【5】案例
from multiprocessing import Process
import os
import time
def run_proc(name):
time.sleep(10)
print('Run child process(子进程) %s (%s)...' % (name, os.getpid()))
def hello_world():
# time.sleep(5)
time.sleep(20)
print('hello world!')
print('Run child process(运行子进程) (%s)...' % (os.getpid()))
if __name__ == '__main__':
print('Parent process(父进程) %s.' % os.getpid())
p1 = Process(target=run_proc, args=('test',))
p2 = Process(target=hello_world)
print("Process will start.")
p1.start()
p2.start()
p1.join()
print('Process end.')
##id号有差错主要看程序运行流程
注意: p1.start() p2.start() p1.join() p2.join()
加入join()阻塞的位置不同,程序运行顺序会不同
# -*- coding:utf-8 -*-
from multiprocessing import Process
import os
import time
def run_proc(name):
for i in range(2):
print(i)
print('【task1-%d】Run child process(子进程) %s (%s)...' % (i, name, os.getpid()))
time.sleep(1)
def hello_world():
# time.sleep(5)
for i in range(3):
print('hello world!')
print('[task2-%d]Run child process(运行子进程) (%s)...' % (i, os.getpid()))
time.sleep(3)
if __name__ == '__main__':
print('Parent process(父进程) %s.' % os.getpid())
p1 = Process(target=run_proc, args=('test',))
p2 = Process(target=hello_world)
print("Process will start.")
p1.start()
p2.start()
p1.join()
p2.join()
print('Process end.')
(1)
p1.start()
p2.start()
p1.join()
p2.join()
(2)
p1.start() p1.join() p2.start() p2.join()
五、多进程编程
【1】方法一:实例对象
"""
Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调⽤对象;
args:表示调⽤对象的位置参数元组;
kwargs:表示调⽤对象的关键字参数字典;
name:为当前进程实例的别名;
group:⼤多数情况下⽤不到;
Process类常⽤⽅法:
is_alive(): 判断进程实例是否还在执⾏;
join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
start(): 启动进程实例(创建⼦进程);
run(): 如果没有给定target参数,对这个对象调⽤start()⽅法时,
就将执 ⾏对象中的run()⽅法;
terminate(): 不管任务是否完成,⽴即终⽌;
"""
from multiprocessing import Process
import time
def task1():
print("正在听音乐")
time.sleep(1)
def task2():
print("正在编程......")
time.sleep(0.5)
def no_multi():
task1()
task2()
#实例化对象
def use_multi():
p1 = Process(target=task1)
p2 = Process(target=task2)
p1.start()
p2.start()
p1.join()
p2.join()
# p.join() 阻塞当前进程, 当p1.start()之后, p1就提示主进程,
# 需要等待p1进程执行结束才能向下执行, 那么主进程就乖乖等着, 自然不会执行p2.start()
# [process.join() for process in processes]
if __name__ == '__main__':
# 主进程
start_time= time.time()
# no_multi()
use_multi()
end_time = time.time()
print(end_time-start_time)
【2】方法二:创建子类
"""
创建子类, 继承的方式
"""
from multiprocessing import Process
import time
class MyProcess(Process):
"""
创建自己的进程, 父类是Process
"""
def __init__(self, music_name):
super(MyProcess, self).__init__()
self.music_name = music_name
def run(self):
"""重写run方法, 内容是你要执行的任务"""
print("听音乐%s" % (self.music_name))
time.sleep(1)
# 开启进程: p.start() ====== p.run()
if __name__ == '__main__':
for i in range(10):
p = MyProcess("音乐%d" % (i))
p.start()
六、进程池
【1】为什么需要进程池Pool?
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
【2】背景
多进程编程能并行执行多个任务,具有效率高,创建方便,运行独立,不受其他进程影响,数据安全等特点;
但进程的创建和删除都需要消耗计算机的资源。如果有大量任务需要多进程完成,且可能需要频繁的创建和删除进程,将给计算机带来大量的资源消耗。
【3】原理
在进程池内运行一定数量的进程,通过这些进程完成进程池队列中的事件,直到事件执行完,减少进程的不断地创建删除过程。
【4】实现
1、创建进程池,在进程池中放入适当的进程
2、将事件加入到进程池队列
3、事件不断运行,直到所有事件运行完毕
4、关闭进程池,回收进程
【5】语句
from multiprocessing import Pool
pool =Pool(processes) #创建进程池对象;参数:表示进程池中有多少进程
pool.apply_async(func,args,kwargs) #将事件放入进程池对列
#参数:func 要执行的1事件
args 给func 用元组传参
kwargs 给func用字典传参
返回值:返回事件对象 通过get()方法获取事件函数返回值
pool.map(func,iter) #将要完成的事件放入进程池
#参数:func 要完成的事件函数, iter 可迭代对象给func传值
#返回值:时间函数的返回值列表
pool.close() #关闭进程池,不能在添加新的事件
pool.join() #阻塞等待回收进程池
【6】方法一:apply_saync`
"""使用"apply_async()"需要自己创建列表,维护返回值'"""
from multiprocessing import Pool
from time import sleep,ctime
def event(message):
sleep(2)
print(ctime(), ':', message)
return '&' + str(message) + '&'
print(ctime())
pool = Pool(4)
r_list = []
for i in range(10):
# apply效率非常低,进程池中同一时刻,只有一个进程在运行。
# 程序在此处阻塞
# element = pool.apply(event,(i,))
# apply_async可以提升效率:进程池中的进程并行执行,一起返回,直到所有进程都执行完毕;
# 程序在此处并不会阻塞
element = pool.apply_async(event, (i,))
print(element)
r_list.append(element)
for e in r_list:
# print(e)
# apply_async返回事件对象,可通过get()方法获取事件函数返回值
print(e.get())
print(ctime())
pool.close()
pool.join()
【7】方法二:map
""" def map(self, func, iterable, chunksize=None):
'''
Apply `func` to each element in `iterable`, collecting the results
in a list that is returned.
'''
return self._map_async(func, iterable, mapstar, chunksize).get()"""
'''方法二 使用进程池的map方法;返回值为列表,无需自己创建维护列表'''
from multiprocessing import Pool
from time import sleep, ctime
def event(message):
sleep(1)
print(ctime(), ':', message)
return '&' + str(message) + '&'
pool = Pool(4)
print(ctime())
# 阻塞,返回值为列表
l = pool.map(event, range(10))
# 非阻塞,返回值为对象,使用get()方法获取列表,get()会阻塞
# l = pool.map_async(event, range(10))
print(l)
#print(l.get())
pool.close()
pool.join()
print(ctime())
【8】processPoolExecutor 进程池
import multiprocessing
def job(id):
print("Start %d...."%(id))
print("End %d...."%(id))
# #创建进程池对象
# pool =multiprocessing.Pool(processes=4)
#
# #给进程池分配任务
# for i in range(10):
# pool.apply_async(job,args=(i+1,))
# pool.close()
#
# #等待所有的子进程执行结束,关闭进程池对象
# pool.join()
#
# print("所有任务执行结束...")
from concurrent.futures import ProcessPoolExecutor
pool = ProcessPoolExecutor(max_workers=4)
pool.map(job,range(10))
【9】案例:多进程与进程池判断素数
def is_prime(num):
if num ==1:
return False
for i in range(2,num):
if num % i ==0:
return False
else:
return True
def task(num):
if is_prime(num):
print("%d是素数" %(num) )
from multiprocessing import Process
def use_multi():
ps =[]
for num in range(1,10000):
#实例化子进程对象
p = Process(target=task,args=(num,))
#开启子进程
p.start()
#储存所有的子进程对象
ps.append(p)
#阻塞子进程,等待所有的子进程执行结束,在执行主进程
[p.join() for p in ps]
def no_multi():
for num in range(1,50000):
task(num)
def use_pool():
from multiprocessing import Pool
from multiprocessing import cpu_count
p = Pool(cpu_count())
""" def map(self, func, iterable, chunksize=None):
'''
Apply `func` to each element in `iterable`, collecting the results
in a list that is returned.
'''
return self._map_async(func, iterable, mapstar, chunksize).get()"""
p.map(task,list(range(1,50000)))
#关闭进程池
p.close()
#阻塞,等待所有的子进程执行结束,在执行主进程
p.join()
if __name__ == '__main__':
import time
start_time =time.time()
no_multi()
# #实验数据1000->0.0049896240234375 10000->0.32386064529418945 50000->6.718240261077881
#use_multi()
#1000->1.1049039363861084 10000->创建太多子进程 OSError: [Errno 24] Too many open files
#use_pool()
# #1000->0.11014962196350098 10000->0.10912609100341797 50000->2.164581537246704
end_time = time.time()
print(end_time -start_time)
由此可见当处理数据量越大的时候,进程池的处理速度性能较好。