Python中的进程
- 一、前言
- 二、创建进程的常用方式
- 1.使用multiprocessing模块创建进程
- 2.使用Process子类创建进程
- 3.使用进程池Pool创建进程
- 三、通过队列实现进程间通信
一、前言
在了解进程之前,我们需要知道多任务概念。多任务,顾名思义,就是指操作系统能够执行多个任务。例如,使用Windows或Linux操作系统可以同时看电影、聊天、查看网页等,此时,操作系统就是在执行多任务,而每一个任务就是一个进程。我们可以打开Windows的任务管理器,可以查看一下系统正在执行的进程,如图所示:
进程(process)是计算机中已运行程序的实体。进程和程序不同,程序本身只是指令、数据及其组织形式的描述,进程才是程序(指令和数据)的真正运行实例。例如,在没有打开QQ时,QQ只是程序。打开QQ后,系统就为QQ开启了一个进程。再打开一个QQ,则又开启了一个进程。
二、创建进程的常用方式
在Python中有多个模块可以创建进程,比较常用的有os.fork()函数、multiprocessing模块和Pool进程池。由于os.fork()函数只适用于Unix/Linux/Mac系统上运行,所以本文重点介绍multiprocessing模块和Pool进程池这两个跨平台模块。
1.使用multiprocessing模块创建进程
multiprocessing模块提供了一个Process类来代表一个进程对象,语法如下:
process(group[,target[,name[,args[,kwargs]]]])Process类的参数说明如下:
- group:参数未使用,值始终为None。
- target:表示当前进程启动时执行的可调用对象。
- name:为当前进程实例的别名。
- agrs:表示传递给target函数的参数元组。
- kwargs:表示传递给target函数的参数字典。
例如,实例化Process类,执行子进程,代码如下:
from multiprocessing import Process# 执行子进程代码
def test(interval):
print("我是子进程")
# 执行主程序
def main():
print("主进程开始")
p = Process(target=test, args=(1,)) # 实例化Process进程类
p.start() # 启动子程序
print("主程序结束")
if __name__ == "__main__":
main()
运行结果如下:
上述代码中,先实例化Process类,然后使用p.start()方法启动子进程,开始执行test()函数。Process的实例p常用的方法除start()外,还有如下常用的方法:
- is_alive():判断进程实例是否还在执行。
- join([timeout]):是否等待进程实例执行结束,或等待多少秒。
- start():启动进程实例(创建子进程)
- run():如果没有给定target参数,对这个对象调用start()方法时,就将执行对象中的run()方法。
- terminate():不管任务是否完成,立即终止。
Process类还有如下常用属性:
- name:当前进程实例别名,默认为Proc-N,N为从1开始递增的整数。
- pid:当前进程实例的PID值。
下面通过一个简单示例演示Process类的方法和属性的使用,创建2个子进程,分别使用os模块和time模块输出父进程和子进程的ID以及子进程的时间,并调用Process类的name和pid属性,代码如下:
# _*_ coding:utf-8 _*_from multiprocessing import Process
import time
import os
# 两个子进程将会调用的两个方法
def child_1(interval):
print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
t_start = time.time() # 计时开始
time.sleep(interval) # 程序将会被挂起interval秒
t_end = time.time() # 计时结束
print("子进程(%s)执行时间为'%0.2f'秒" % (os.getppid(), t_end - t_start))
def child_2(interval):
print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
t_start = time.time() # 计时开始
time.sleep(interval) # 程序将会被挂起interval秒
t_end = time.time() # 计时结束
print("子进程(%s)执行时间为'%0.2f'秒" % (os.getppid(), t_end - t_start))
if __name__ == "__main__":
print("-----------------父进程开始执行-------------")
print("父进程PID:%s" % os.getpid()) # 输出当前程序的PID
p1 = Process(target=child_1, args=(1,)) # 实例化进程p1
p2 = Process(target=child_2, name='mrsoft', args=(1,)) # 实例化进程p2
p1.start() # 启动进程p1
p2.start() # 启动进程p2
# 同时父进程乃然往下执行,如果p2进程还在执行,将会返回True
print("p1.is_alive=%s" % p1.is_alive())
print("p2.is_alive=%s" % p2.is_alive())
# 输出p1和p2进程的别名和PID
print("p1.is_alive = %s" % p1.name)
print("p1.pid=%s" % p1.pid)
print("p2.is_alive = %s" % p2.name)
print("p2.pid=%s" % p2.pid)
print("----------------等待子进程-------------------")
p1.join() # 等待p1进程结束
p2.join() # 等待p2进程结束
print("----------父进程执行结束----------------------")
上述代码中,第一次实例化Process类时,会为name属性默认赋值为“Process-1”,第二次则默认为“Process-2”,但是由于在实例化进程p2时,设置了name属性为“mrsoft”,所以p2.name的值为“mrsoft”而不是“Process-2”。程序运行流程示意图如图所示:
运行结果如图所示:
2.使用Process子类创建进程
对于一些简单的小任务,通常使用Process(target=test)方式实现多进程。但是如果要处理复杂任务的进程,通常定义一个类,使其进程Process类,每次实例化这个类的时候,就等同于实例化一个进程对象。下面,通过一个实例来学习一下如何通过使用Process子类创建多个进程。
使用Process子类方式创建2个子进程,分别输出父、子进程的PID,已经子进程的运行状态和运行时间,代码如下:
# _*_ coding:utf-8 _*_from multiprocessing import Process
import time
import os
# 继承Process类
class Subprocess(Process):
# 由于Process类本身也有__init__初始化方法,这个子类相当于重写了父类的这个方法
def __init__(self, interval, name=''):
Process.__init__(self) # 调用Process父类的初始化方法
self.interval = interval # 接收参数interval
if name:
self.name = name # 如果传递参数name,则为子进程创建name属性,否则使用默认属性
# 重写了Process类的run()方法
def run(self):
print("子进程(%s)开始执行,父进程为(%s)" % (os.getpid(), os.getppid()))
t_start = time.time()
time.sleep(self.interval)
t_stop = time.time()
print("子进程(%s)执行结束,耗时%0.2f秒" % (os.getpid(), t_stop - t_start))
if __name__ == "__main__":
print("------------父进程开始执行-----------")
print("父进程PID:%s" % os.getpid()) # 输出当前程序的ID
p1 = Subprocess(interval=1, name='mrsoft')
p2 = Subprocess(interval=2)
# 对一个不包含target属性的Process类执行start()方法,就会运行这个类中的run()方法
# 所以这里会执行p1.run()
p1.start() # 启动p1进程
p2.start() # 启动p2进程
# 输出p1和p2进程的执行状态,如果真正进行,返回True;否则返回False
print("p1.is_alive=%s" % p1.is_alive())
print("p2.is_alive=%s" % p2.is_alive())
# 输出p1和p2进程的别名和PID
print("p1.name = %s" % p1.name)
print("p1.pid=%s" % p1.pid)
print("p2.name = %s" % p2.name)
print("p2.pid=%s" % p2.pid)
print("----------------等待子进程-------------------")
p1.join() # 等待p1进程结束
p2.join() # 等待p2进程结束
print("----------父进程执行结束----------------------")
上述代码中,定义一个Subprocess子类,继承mulitprocess.Process父类。Subprocess子类中定义了2个方法:__ inti__()初始化方法和run方法。在__ inti__()初始化方法中,调用mulitprocess.Process父类的__ inti__()初始化方法,否则父类初始化方法将被覆盖,无法开启进程。此外在Subprocess子类中并没有定义start()方法,但主进程中却调用了start()方法,此时就会自动执行Subprocess类的run()方法。运行结果如下图所示:
3.使用进程池Pool创建进程
前面我们使用Process类创建了2个进程。如要要创建几十个或者上百个进程,则需要更多个Process类。有没有更好的创建进程方式解决这类问题呢?答案就是使用mulitprocessing模块提供的Pool类,即Pool进程池。
为了更好的理解进程池,可以将进程池比作水池,如图所示。我们需要完成放满10个水盆的水的任务,而这个水池中,最多打开3个水龙头开始放水,也就是同时可以执行3个任务,即开启3个进程。为了更快的完成任务,现打开3个水龙头开始放水,当有一个水盆的水接满时,即该进程完成1个任务,我们就将这个水盆的水倒入水桶中,然后继续接水,即执行下一个任务。如果3个水盆同时装满水,那么在放满第9盆水,系统会随机分配1个水盆接水,另外两个水盆闲着。
接下来,先来了解一下Pool类的常用方法。常用方法及说明如下:
- apply_async(func[,args[,kwds]]):使用非阻塞方式调用func()函数(并执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递func()函数的参数列表,kwds为传递给func()函数的关键字参数列表。
- apply(func[,args[,kwds]]):使用阻塞方式调用func()函数。
- close():关闭Pool,使其不再接受新的任务。
- terminate():不管任务是否完成,立即终止。
- join():主进程阻塞,等待子进程的退出,必须在close或terminate之后使用。
在上面的方法提到了apply_async()使用非阻塞方式调用函数,而apply()使用阻塞方式调用函数。那么什么又是阻塞和非阻塞呢?在下图所示,分别使用阻塞方式和非阻塞方式执行3个任务。如果使用阻塞方式,必须等待上一个进程退出才能执行下一个进程,而使用非阻塞方式,则可以并行执行3个进程。
下面通过一个示例演示一下如何使用进程池创建多进程。这里模拟水池放水的场景,定义一个进程池,设置最大进程数为3。然后使用非阻塞方式执行10个任务,查看每个进程执行的任务。具体代码如下:
# _*_ coding:utf-8 _*_from multiprocessing import Pool
import time
import os
def task(name):
print("子进程(%s)执行task %s ..." % (os.getpid(), name))
time.sleep(1) # 休眠1秒
if __name__ == "__main__":
print("父进程(%s)." % os.getppid())
p = Pool(3) # 定义一个进程池,最大进程数为3
for i in range(10):
p.apply_async(task, args=(i,)) # 使用非阻塞方式调用函数task()函数
print("等待所有子进程结束...")
p.close()
p.join()
print("所有子进程结束.")
运行结果如图所示,从图中可以看出6588的子进程执行了4个任务,而其余2个子进程分别执行了3个任务。
三、通过队列实现进程间通信
预知详情,请期待下篇,关注 ζ小菜鸡,让我们一起学习,咱们顶峰相见(๑•̀ㅂ•́)و✧