python并发编程之多进程
在讲解python多进程之前,我们先引出一个话题,什么话题呢?“多任务”,那么什么是多任务呢,它是干什么的呢,来让我们一起来看看吧!
一、多任务
1、什么是多任务?
(1)在python中所了解到的函数执行,遇到多个函数调用,它的执行方式是按照函数调用的顺序依次挨个执行,基本上不可能出现多个函数同时执行的现象。
(2)那么,也就是说我们所写的程序都是但任务的,一个函数或者方法执行完毕,另一个函数或者方法才能正常执行。如果想要多个函数或方法同时调用执行,需要使用多任务。
(2)那么为什么要用多任务的方式去同时执行多个函数呢?因为多任务的最大好处就是:充分利用cpu,提高我i们程序的执行效率
2、多任务的概念?
多任务是指在同一时间内,去执行多个任务,一个任务可以视之为为一个应用程序。目前的操作系统都是多任务的操作系统,可以同时运行多个软件。
3、多任务的执行方式
(1)并发
所谓并发,就是指:在同一时间内去交替执行多个任务
例如:
对于单核cpu处理多任务:操作系统会轮流让各个软件交替执行。假如,我们开了3款软件,qq、微信、网易云音乐。那面操作系统会让qq执行一段时间(假如是0.01秒),然后切换到微信,再去执行一段时间,然后继续切换到网易云音乐,再去执行一段时间……反复执行下去。表面上看这些软件都在同时运行,但实则是轮流挨个执行,因为CPU的计算速度太快,我们根本有肉眼无法看到。
(2)并行
所谓并行,是指:在同一时间内去给CPU每个内核挨个分配一个任务,当任务超过内核数,那么将和并发的处理机制一样,再给每个内核配发任务,也就是说 并行要在并发的基础上进行
例如:
对于多核CPU处理多任务,操作系统会给CPU的每个内核去分配一个执行的软件,当开的任务数超过内核数,那么处理机制就会和并发的一样,也就是说每个内核都会去执行多个不同的任务。多个内核才是真真正正的多个软件同时运行,注意:多核CPU是以并行的方式去执行多任务,始终有多个软件一起执行。
二、进程
1、什么是进程
在python中,想要是实现多任务,可以使用进程来完成,使用进程是完成多任务的一种方式
2、进程的概念
在计算机中,正在运行的程序或者软件,就是一个进程。进程是操作系统进行资源分配的最基本单位,也就是说每开启一个软件(进程),操作系统都会给其分配一定的运行资源(内存资源),保证其正常运行。
例如:
生活中的工厂(计算机),每一个车间就是一个进程,工厂提供资源(地方、电力设备……等),真正能够干活是车间的员工,员工可以理解为线程。
注意:
一个程序的运行至少有一个进程,一个进程默认有一个线程,进程里边可以创建多个子进程,线程是依附在进程里边的,没有进程就没有线程。
3、进程的处理方式
(1)单进程
默认程序运行都需要去创建一个进程,一个python文件的运行,就是开启一个进程去处理,该python文件就被称为主进程。
进程中的场景:主线程去执行代码
(2)多进程
一个python文件的运行,占用一个进程去处理,那么假如要同时运行两个python文件,那么就要给第二个python文件去开启一个进程去处理
多进程可以实现多任务,每个进程就好比一个独立的车间,每个车间都各自在运行,也就是说每个进程也各自在运行,执行各自的任务。
三、进程的状态
在了解怎么使用多进程之前,我们首先了解一下进程的三个状态。根据进程执行时的间断性,决定了进程可能有多种状态,但事实上,运行中的进程具有以下三种基本状态:
1、就绪状态:
就绪状态,英文名Ready,进程已获得除处理器外的所有资源,只是在等待分配处理器资源,只要分配了处理器资源,进程就可以执行,例如我们的python文件,在不运行时就是处于就绪状态,以及主进程中的子进程不开启就是处于就绪状态。
2、运行状态
运行状态,英文名称Running,进程占用处理器资源,处于此状态的进程的数目小于等于处理器的数目,在没有其他进程可执行时(如所有进程都进入阻塞状态),系统通常会自动执行系统的空闲进程
3、阻塞状态
阻塞状态,英文名Blocked,系统由于进程等待某种条件(比如输入输出函数,进程的join方法),在满足条件之前无法继续执行。该事件发生前,即使把处理器资源分配给该进程,该进程也无法运行。
进程三态转换图:
四、多进程的使用
1、multiprocessing 模块介绍
python中的多线程无法利用多核优势,如果想要充分的使用多核CPU的资源,在python中大部分情况需要使用多进程。python中提供了 multipricessing 模块,该模块用于开启进程,并在子进程中执行我们定制的任务,比如函数。所有说要想使用多进程就必须导入 multiprocessing 模块。
# 导入multiprocessing模块
import multiprocessing
# 导入multiprocessing模块下的Process类
from multiprocessing import Process
2、Process 类的的介绍和使用
(1) Process 进程类的介绍
# 实例化进程
r1 = Process([group, target, name, args, kwargs])
# group :指定进程组,目前只能使用None
# target :执行的目标任务名称
# name :进程的名称
# args :以元组方式给执行的任务传递参数
# kwargs :以字典的方式给执行的任务传递参数
(2)Process 实例对象常用方法和属性
1、start( ) : 表示启动创建的子进程
from multiprocessing import Process
import time
def run(name,num):
start = time.time()
for i in range(1,num):
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(name,num):
start = time.time()
time.time()
for i in range(1,num):
print(f"{name}唱了第{i}首歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
r1 = Process(group=None,target=run,name="run",args=("小明",5))
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
# 此时将同步执行r1和s1线程,或者说这两个函数
r1.start()
s1.start()
2、join( ) : 表示等待子进程执行结束,相当于给该子进程做了一个单独的检测,当该子进程执行结束之后,主进程或其他进程才能继续往后执行
from multiprocessing import Process
import time
def run(name,num):
start = time.time()
for i in range(1,num):
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(name,num):
start = time.time()
time.time()
for i in range(1,num):
print(f"{name}唱了第{i}首歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
# 此时只有将r1进程全部运行完毕后才去执行主进程main,然后去执行子进程s1
r1.join()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
print(f"run函数{r1.name}")
print(f"sing函数{s1.name}")
3、terminate( ) :表示不管该子进程的任务是否完成,立即终止该子进程
from multiprocessing import Process
import time
def run(name,num):
start = time.time()
for i in range(1,num):
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(name,num):
start = time.time()
time.time()
for i in range(1,num):
print(f"{name}唱了第{i}首歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
# 此r1还没开始就被强制结束
r1.terminate()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
print(f"run函数{r1.name}")
print(f"sing函数{s1.name}")
4、name : 当前进程的别名,默认为 Process-N,N为从1开始的递增整数
**注意:**一般情况下主进程会体现运行然后等待其他子进程执行完毕后结束
from multiprocessing import Process
import time
def run(name,num):
start = time.time()
for i in range(1,num):
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(name,num):
start = time.time()
time.time()
for i in range(1,num):
print(f"{name}唱了第{i}首歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
print(f"run函数{r1.name}")
print(f"sing函数{s1.name}")
3、获取进程编号
(1)获取当前进程的编号
方法:使用os模块下的getpid( )方法
os.getpid()
(2)获取当前进程的父进程的编号
方法:使用os模块下的 getppid( ) 方法
os.getppid()
例子:
from multiprocessing import Process
import time,os
def run(name,num):
print(f"run进程的id:{os.getpid()}")
print(f"main进程的id:{os.getppid()}")
start = time.time()
for i in range(1,num):
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(name,num):
print(f"sing进程的id:{os.getpid()}")
print(f"main进程的id:{os.getppid()}")
start = time.time()
time.time()
for i in range(1,num):
print(f"{name}唱了第{i}首歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
print(f"main进程的id:{os.getpid()}")
r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
至此,我们发现一个规律:
所有的子进程都来自于主进程,即 main ,因此只要得知一个程序中的主进程编号,那么该程序中的子进程编号按照主进程编号为起始值加一计算
(3)获取当前进程的详细信息
# multiprocessing.current.process()
# 说明:用于判断该进程使子进程还是主进程,并且获取该进程的 别名和该进程的父进程编号
# 例如:
# 子进程 别名:run 父进程编号 开启状态
<Process name='run' parent=5548 started>
# 主进程 名称 主进程没有父进程,所有没有编号 进程处于开启状态
<_MainProcess name='MainProcess' parent=None started>
(4)获取进程编号的作用
获取进程编号的目的主要数为了验证 主进程 和 子进程 之间的关系,可以得知子进程是由哪个主进程创建出来的
4、主进程创建守护进程
守护进程是Process类的实例对象所使用的布尔类型的属性,守护进程标志:Daemon属性
当 子进程实例对象.Daemon = True 时表示该子进程被主进程守护,那么生命周期也在主进程执行的范围内,当主进程执行完毕,守护子进程被干掉,但是此时主进程不一定退出,因为有时候主进程还会等待非守护进程的执行。
import time,os,multiprocessing
def run(name,num):
print(f"run进程的id:{os.getpid()}")
print(f"main进程的id:{os.getppid()}")
print(multiprocessing.current_process())
start = time.time()
for i in range(1,num):
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
print(f"main进程的id:{os.getpid()}")
r1 = multiprocessing.Process(group=None,target=run,name="run",args=("小明",5))
# 守护进程
r1.daemon = True
r1.start()
print(multiprocessing.current_process())
总结:
(1)被主进程守护的子进程,在主进程执行完毕后就被干掉
(2)守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
(3)要在开启子进程之前,设置当前子进程被主进程守护
(3)如果子主进程内有守护子进程,并且还要非守护子进程,那么当主进程执行完毕后会等待非守护进程执行完毕后才结束,而守护子进程在主进程执行完毕后就被敢干掉。
5、杀死字进程
# 在python中使用os模块下的 kill 方法来杀死进程
os.kill(__pid, __signal)
# 参数:
# 1、__pid :该子进程的编号 os.get.getpid()
# 2、__signal :接收一个int类型的编号,一般情况下为9,表示杀死进程,即强制结束该进程
__signal参数:信号编码:
示例:
import time,os,multiprocessing
def run(name,num):
start = time.time()
for i in range(1,num):
if i == 4:
# 当i=4的时候,r1进程就被杀死,下来只执行s1进程,主进程等待s1进程执行完毕后退出
os.kill(os.getpid(), 9)
print(f"{name}跑了第{i}圈")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(name,num):
start = time.time()
time.time()
for i in range(1,num):
print(f"{name}唱了第{i}首歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
r1 = multiprocessing.Process(group=None,target=run,name="run",args=("小明",6))
s1 = multiprocessing.Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":6})
s1.start()
r1.start()
6、进程的特征
(1)进程之间不共享全局变量
演示:
import time,os,multiprocessing
def run(num):
start = time.time()
for i in range(1,num):
# 添加元素
my_list.append(i)
time.sleep(1)
end = time.time()
print(f"r1进程中的元素:{my_list}")
print("{:.0f}s".format(end-start))
def sing(num):
start = time.time()
time.time()
for i in range(num):
# 添加元素
my_list.append(i)
time.sleep(1)
end = time.time()
print(f"s1进程中的元素:{my_list}")
print("{:.0f}s".format(end - start))
my_list = list()
r1 = multiprocessing.Process(group=None,target=run,name="run",args=(6,))
s1 = multiprocessing.Process(group=None,target=sing,name="sing",kwargs={"num":4})
s1.start()
r1.start()
print(f"主进程中的元素:{my_list}")
输出结果:
主进程中的元素:[]
s1进程中的元素:[0, 1, 2, 3]
4s
r1进程中的元素:[1, 2, 3, 4, 5]
5s
试想一下,为什么他们之间的元素都不一样,并且不是同一个列表呢?
结论:
当一个进程对全局变量中的数据进行修改,对于其他进程而言,不会造成任何影响,可以理解为每个进程拿的都是最初的全局变量,或者可以理解为全局变量就是所谓的资源,当创建一个进程,则系统会直接给这个进程复制一个全局变量,针对于这个全局变量而言,再进程之间都是相互独立存在的,之间没有任何的关系。
关系图:
(2)主进程会等待所有子进程结束以后再结束
1、所有子进程结束主进程才会结束,那么也就是说当子进程没有执行完毕,你就结束主继承,那么是无法做到的
import time,os,multiprocessing
def run(num):
start = time.time()
for i in range(1,num):
print("跑步")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end-start))
def sing(num):
start = time.time()
time.time()
for i in range(num):
# 添加元素
print("唱歌")
time.sleep(1)
end = time.time()
print("{:.0f}s".format(end - start))
my_list = list()
r1 = multiprocessing.Process(group=None,target=run,name="run",args=(6,))
s1 = multiprocessing.Process(group=None,target=sing,name="sing",kwargs={"num":4})
s1.start()
r1.start()
# 表示默认退出程序,在这里是没办法退出的,因为主进程要等待所有子进程结束才可以结束
exit()
2、在主进程结束之间,手动结束了所有的子进程,那么程序的结束由主进程来控制
7、那么如何做到主程序结束则整个程序结束?
(1)在主进程结束之前保证所有子进程都使用 terminate( ) 方法去终止子进程
(2)在子进程开启之前,设置所有子进程被主进程守护,即所有子进程的属性 daemon = True