第十四章 多进程

35 篇文章 3 订阅
25 篇文章 0 订阅

目录

一、多进程

1.1 定义

1.2 进程的特征

1.3 进程切换

1.4 进程运行状态

1.4.1 进程状态转换

1.5 同步/异步、阻塞/非阻塞

1.6 进程的创建过程

1.7 进程的终止过程

1.7.1 引起进程终止的事件

1.7.2 进程的终止过程

1.8 进程的阻塞过程

1.8.1 引起进程阻塞和唤醒的事件

1.8.2 进程阻塞过程

1.8.3 进程唤醒过程

1.9 进程调度算法

二、python进程

2.1 os.fork()

2.2 multiprocessing模块

2.2.1 Process类

2.2.2 Pool进程池

2.2.3 进程同步:消息队列Queue&JoinableQueue

2.2.4 进程同步:加锁-Lock

2.2.5 进程同步:加锁-Semaphore

2.2.6  进程同步:信号传递-Event

2.2.7 进程同步:使用管道-Pipe

2.2.8 进程同步:使用Condition

2.2.9 进程同步: 共享变量(数字/字符串/列表/字典/实例对象)

2.2.10 进程日志

2.2.11 守护进程 

 2.3 subprocess模块

2.3.1 subprocess模块中的常用函数

2.3.2 各函数的定义及参数说明

2.3.3 subprocess.Popen类

2.3.4 subprocess.call()


一、多进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础,进程是线程的容器

1.1 定义

狭义定义:进程是正在运行的程序的实例( an instance of a computer program that is being executed)
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程是一个内核级的实体,进程结构的所有成分都在内核空间中,一个用户程序不能直接访问这些数据

进程的概念主要有两点:

第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令(比如错误信息打印的调用关系)和本地变量。

第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时,它才能成为一个活动的实体,我们称其为进程。

1.2 进程的特征

  • 动态性:进程的实质是程序在多任务系统中的一次执行过程,进程是动态产生,动态消亡的。
  • 并发性:任何进程都可以同其他进程一起并发执行
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度执行
  • 结构特征:进程由程序、数据和进程控制块三部分组成。
  • 多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

1.3 进程切换

CPU的组成

CPU由运算器,控制器和寄存器组成。运算器是对数据进行加工处理的部件,它不仅可以实现基本的算术运算,还可以进行基本的逻辑运算,实现逻辑判断的比较及数据传递,移位等操作;控制器是负责从存储器(内存和硬盘)中取出指令,确定指令类型及译码,按时间的先后顺序向其他部件发出控制信号,统一指挥和协调计算机各器件进行工作的部件;寄存器是CPU内部高速独立的暂时存储单元(容量很小)。CPU是按照时间片进行轮询对进程进行处理的。

进程切换

进程切换就是从正在运行的进程中收回处理器,然后再使待运行进程来占用处理器

从某个进程收回处理器,实质上就是把进程存放在处理器寄存器中的中间数据存放在进程的私有堆栈,从而把处理器的寄存器腾出来让其他进程使用。

让进程来占用处理器,实质上是把某个进程存放在私有堆栈中寄存器的数据(前一次本进程被中止时的中间数据)再恢复到处理器的寄存器中去,并把待运行进程的断点送入处理器的程序指针,于是待运行进程就开始被处理器运行了,也就是这个进程已经占有处理器的使用权了。

一个进程存储在处理器各寄存器中的中间数据叫做进程的上下文,分为用户级上下文/寄存器上下文/系统上下文。进程的切换实质上就是被中止运行进程与待运行进程上下文的切换。在进程未占用处理器时,进程的上下文是存储在进程的私有堆栈中的。

1.4 进程运行状态

进程执行时的间断性,决定了进程可能具有多种状态,一般有以下5种状态:

 就绪状态(Ready):

进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。

  • 运行状态(Running):

进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。

阻塞状态(Blocked):

由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理机分配给该进程,也无法运行。注意:当I/O完成或者进程等待的事件发生后,进程进入就绪状态而不是运行状态,要等待CPU的轮询处理

新建状态: 
进程刚刚被创建时没有被提交的状态,并等待系统完成创建进程的所有必要信息。 进程正在创建过程中,还不能运行。操作系统在创建状态要进行的工作包括分配和建立进程控制块表项、建立资源表格(如打开文件表)并分配资源、加载程序并建立地址空间表等。创建进程时分为两个阶段,第一个阶段为一个新进程创建必要的管理信息,第二个阶段让该进程进入就绪状态。由于有了新建态,操作系统往往可以根据系统的性能和主存容量的限制推迟新建态进程的提交。

终止状态:
进程已结束运行,回收除进程控制块之外的其他资源,并让其他进程从进程控制块中收集有关信息(如记帐和将退出代码传递给父进程)。类似的,进程的终止也可分为两个阶段,第一个阶段等待操作系统进行善后处理,第二个阶段释放主存。

1.4.1 进程状态转换

由于进程的不断创建,系统资源特别是主存资源已不能满足所有进程运行的要求。这时,就必须将某些进程挂起,放到磁盘对换区,暂时不参加调度,以平衡系统负载,当条件允许的时候,会被操作系统再次调回内存,重新进入等待被执行的状态即就绪态;进程挂起的原因可能是系统故障,或者是用户调试程序,也可能是需要检查问题。 

  • 活跃就绪:是指进程在主存并且可被调度的状态。
  • 静止就绪(挂起就绪):是指进程被对换到辅存时的就绪状态,是不能被直接调度的状态,只有当主存中没有活跃就绪态进程,或者是挂起就绪态进程具有更高的优先级,系统将把挂起就绪态进程调回主存并转换为活跃就绪。
  • 活跃阻塞:是指进程已在主存,一旦等待的事件产生便进入活跃就绪状态。
  • 静止阻塞:是指进程对换到辅存时的阻塞状态,一旦等待的事件产生便进入静止就绪。

注意:只要是挂起的进程,也就是静止就绪或者静止阻塞的进程都是放入磁盘中的,是不能被直接调度的。被激活后要先放入内存才能调度

1.5 同步/异步、阻塞/非阻塞

同步:顺序执行
异步:不需要按顺序执行
有依赖关系时只能用同步,没有依赖关系可同步可异步

阻塞:正在运行的进程由于提出系统服务请求(如I/O操作),但因为某种原因未得到操作系统的立即响应,或者需要从其他合作进程获得的数据尚未到达等原因,该进程只能调用阻塞原语把自己阻塞,等待相应的事件出现后才被唤醒
非阻塞:无需等待

1.6 进程的创建过程

一旦操作系统发现了要求创建新进程的事件后,便调用进程创建原语Creat()按下述步骤创建一个新进程。
1) 申请空白PCB(进程控制块):为新进程申请获得唯一的数字标识符,并从PCB集
合中索取一个空白PCB。
2) 为新进程分配资源:为新进程的程序和数据以及用户栈分配必要的内存空间。显然此时操作系统必须知道新进程所需要的内存大小。
3) 初始化进程控制块。PCB的初始化包括:
①初始化标识信息,将系统分配的标识符和父进程标识符,填入新的PCB中。
②初始化处理机状态信息,使程序计数器指向程序的入口地址,使栈指针指向栈顶。
③初始化处理机控制信息,将进程的状态设置为就绪状态或静止就绪状态,将它设置为最低优先级,除非用户以显式的方式提出高优先级要求。
4)将新进程插入就绪队列,如果进程就绪队列能够接纳新进程,便将新进程插入到就绪队列中。

1.7 进程的终止过程

1.7.1 引起进程终止的事件

1)正常结束

在任何计算机系统中,都应该有一个表示进程已经运行完成的指示。例如,在批处理系统中,通常在程序的最后安排一条Hold指令或终止的系统调用。当程序运行到Hold指令时,将产生一个中断,去通知操作系统本进程已经完成。

2)异常结束

在进程运行期间,由于出现某些错误和故障而迫使进程终止。这类异常事件很多常见的有:越界错误,保护错,非法指令,特权指令错,运行超时,等待超时,算术运算错,I/O故障。

3)外界干预

外界干预并非指在本进程运行中出现了异常事件,而是指进程应外界的请求而终止运行。这些干预有:操作员或操作系统干预,父进程请求,父进程终止。

1.7.2 进程的终止过程

如果系统发生了上述要求终止进程的某事件后,OS便调用进程终止原语,按下述过程去终止指定的进程。

1)根据被终止进程的标识符,从PCB集合中检索出该进程的PCB,从中读出该进程状态。

2)若被终止进程正处于执行状态,应立即终止该进程的执行,并置调度标志为真,用于指示该进程被终止后应重新进行调度。

3)若该进程还有子孙进程,还应将其所有子孙进程予以终止,以防他们成为不可控的进程。

4)将被终止的进程所拥有的全部资源,或者归还给其父进程,或者归还给系统。

5)将被终止进程(它的PCB)从所在队列(或链表)中移出,等待其它程序来搜集信息。

1.8 进程的阻塞过程

1.8.1 引起进程阻塞和唤醒的事件

1)请求系统服务

当正在执行的进程请求操作系统提供服务时,由于某种原因,操作系统并不立即满足该进程的要求时,该进程只能转变为阻塞状态来等待,一旦要求得到满足后,进程被唤醒。

2)启动某种操作

当进程启动某种操作后,如果该进程必须在该操作完成之后才能继续执行,则必须先使该进程阻塞,以等待该操作完成,该操作完成后,将该进程唤醒。

3)新数据尚未到达

对于相互合作的进程,如果其中一个进程需要先获得另一(合作)进程提供的数据才能运行以对数据进行处理,则要是其所需数据尚未到达,该进程只有(等待)阻塞,等到数据到达后,该进程被唤醒。

4)无新工作可做

系统往往设置一些具有某特定功能的系统进程,每当这种进程完成任务后,便把自己阻塞起来以等待新任务到来,新任务到达后,该进程被唤醒。

1.8.2 进程阻塞过程

正在执行的进程,当发现上述某事件后,由于无法继续执行,于是进程便通过调用阻塞原语block把自己阻塞。进程的阻塞是进程自身的一种主动行为。进入block过程后,由于此时该进程还处于执行状态,所以应先立即停止执行,把进程控制块中的现行状态由执行改为阻塞,并将PCB插入阻塞队列。如果系统中设置了因不同事件而阻塞的多个阻塞队列,则应将本进程插入到具有相同事件的阻塞(等待)队列。最后,调度程序进行重新调度,将处理机分配给另一就绪进程,并进行切换。

1.8.3 进程唤醒过程

当被阻塞的进程所期待的事件出现时,如I/O完成或者其所期待的数据已经到达,则由有关进程(比如,用完并释放了该I/O设备的进程)调用唤醒原语wakeup,将等待该事件的进程唤醒。唤醒原语执行的过程是:首先把被阻塞的进程从等待该事件的阻塞队列中移出,将其PCB中的现行状态由阻塞改为就绪,然后再将该PCB插入到就绪队列中。

原语(原子操作):是由若干条指令组成的,用于完成一定功能的一个过程。具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。

1.9 进程调度算法

  • FIFO(First Input First Output 先进先出法)
  • RR(时间片轮转算法)
  • HPF(最高优先级算法)

二、python进程

Python中一些进程模块:
1)os.fork()(只能在linux中使用)
2)subprocess
3)processing
4)Multiprocessing
python中实现进程通信有以下几种方式:
1)文件
2)管道(Pipes)
3)Socket
4)信号
5)信号量
6)共享内存

2.1 os.fork()

#encoding=utf-8
import os
print(os.getpid()) #打印当前主进程的pid
pid = os.fork() # 创建一个子进程,创建后,有2个进程同时运行。
print (pid) #子进程id和0
if pid == 0:
	print ('I am child process (%s) and my parent is %s.' % (os.getpid(), os.getppid()))
else:
	print ('I (%s) just created a child process (%s).' % (os.getpid(), pid))

fork()函数, 它也属于一个内建函数,并且只在Linux系统下存在。它非常特殊。普通的函数调用,调用一次,返回一次,但是fork()调用一次,返回两次,因为操作系统自动把当前进程(称为父进程)复制了一份(称为子进程),然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的PID。这样做的理由是,一个父进程可以fork()出很多子进程,所以,父进程要记下每个子进程的ID,而子进程只需要调用getppid()就可以拿到父进程的ID,子进程只需要调用os.getpid()函数可以获取自己的进程号。

2.2 multiprocessing模块

multiprocessing模块就是跨平台版本的多进程管理包,支持子进程、通信和共享数据、执行不同形式的同步。这个模块表示像线程一样管理进程,这个是multiprocessing的核心,它与threading很相似,对多核CPU的利用率会比threading好的多。该模块中有以下类/和方法:

>>> import multiprocessing
>>> dir(multiprocessing)
['Array', 'AuthenticationError', 'Barrier', 'BoundedSemaphore', 'BufferTooShort', 'Condition', 'Event', 'JoinableQueue', 'Lock', 'Manager', 'Pipe', 'Pool', 'Process', 'ProcessError', 'Queue', 'RLock', 'RawArray', 'RawValue', 'SUBDEBUG', 'SUBWARNING', 'Semaphore', 'SimpleQueue', 'TimeoutError', 'Value', '__all__', '__builtins__', '__cached__', '__doc__', '__file__', '__loader__', '__name__', '__package__', '__path__', '__spec__', 'active_children', 'allow_connection_pickling', 'context', 'cpu_count', 'current_process', 'freeze_support', 'get_all_start_methods', 'get_context', 'get_logger', 'get_start_method', 'log_to_stderr', 'parent_process', 'process', 'reducer', 'reduction', 'set_executable', 'set_forkserver_preload', 'set_start_method', 'sys']

2.2.1 Process类

Multiprocessing模块创建进程使用的是Process类。

➢Process类的构造方法:
help(multiprocessing.Process)
__init__(self, group=None, target=None, name=None, args=(), kwargs={})
➢参数说明:
group: 进程所属组,基本不用。
target: 表示调用对象,一般为函数。
name: 进程别名。
args: 表示调用对象的位置参数元组。
kwargs: 表示调用对象的字典。

Process的常用方法:

1.start():启动子进程实例(创建子进程),子进程处于就绪态

2.is_alive():判断子进程是否还在活着

3.join([timeout]):等待子进程执行结束,或等待多少秒

4.terminate():不管任务是否完成,立即终止子进程

 

#coding=utf-8
import multiprocessing
import time
def do(n) :
	#获取当前线程的名字
	name = multiprocessing.current_process().name#current_process函数返回当前进程
	print(name,'starting')
	time.sleep(2)
	print("worker ", n)
	return
if __name__ == '__main__':
	start = time.time()
	numList = []#存放进程对象的list
	for i in range(5):
		p = multiprocessing.Process(name="光荣之路", target=do, args=(i,))
		numList.append(p)
		p.start()#启动执行新建进程的任务,进程会调用do函数来执行
		p.join()#等待当前进程执行完毕后,才会执行主进程后续代码(生成一个进程就要等它执行任务完毕后才进入第二次循环,没实现并发)
		print("Process end.")
	print(numList)
	end = time.time()
	print("执行耗时:",  end-start)

#coding=utf-8
import multiprocessing
import time
def do(n) :
	#获取当前线程的名字
	name = multiprocessing.current_process().name
	print(name,'starting')
	time.sleep(2)
	print("worker ", n)
	return
if __name__ == '__main__':
	start = time.time()
	numList = []#存放进程对象的list
	for i in range(5):
		p = multiprocessing.Process(name="光荣之路", target=do, args=(i,))
		numList.append(p)
		p.start()#启动执行新建进程的任务,进程会调用do函数来执行
		print("Process end.")
	print(numList)
	for i in numList: #实现并发
		i.join()#每个进程执行结束才会开始下一个循环
	end = time.time()
	print("执行耗时:",  end-start)

 

 多进程模板

#coding=utf-8
import multiprocessing
import urllib.request
import time
def func1(url) :
	response = urllib.request.urlopen(url)
	html = response.read()
	print(html[0:20])
	time.sleep(1)

def func2(url) :
	response = urllib.request.urlopen(url)
	html = response.read()
	print(html[0:30])
	time.sleep(1)

if __name__ == '__main__' :
	p1 = multiprocessing.Process(target=func1,args=("http://www.sogou.com",),name="gloryroad1")
	p2 = multiprocessing.Process(target=func2,args=("http://www.baidu.com",),name="gloryroad2")
	p1.start()
	p2.start()
	p1.join()
	p2.join()
	time.sleep(1)
	print("done!")

测试单进程和多进程程序执行的效率

#coding: utf-8
import multiprocessing
import time
def m1(x):#返回平方值
	time.sleep(0.01)
	return x * x

if __name__ == '__main__':
	#生成一个进程池,multiprocessing.cpu_count()返回当前机器的cpu核数
	pool = multiprocessing.Pool(multiprocessing.cpu_count())
	i_list = range(1000)
	time1=time.time()
	pool.map(m1, i_list)
	time2=time.time()
	print('time elapse:',time2-time1)
	time1=time.time()
	list(map(m1, i_list))#表示用单进程
	time2=time.time()
	print('time elapse:',time2-time1)

2.2.2 Pool进程池

在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作的对象数目不大时,还可以直接使用Process类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时进程池就派上用场了。
Pool类可以提供指定数量(一般为CPU的核数)的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。

注意:进程池中的进程是不能共享队列和数据的,而Process生成的子进程可以共享队列

Pool类中常用方法

  • apply():

函数原型:apply(func[, args=()[, kwds={}]])

该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不再使用)。阻塞的,和单进程没有什么区别

  • apply_async():

函数原型:apply_async(func, args=(), kwds={}, callback=None, error_callback=None)

异步非阻塞的,不用等待当前进程执行完毕,随时根据系统调度来进行进程切换(建议使用

注意:apply_async每次只能提交一个进程的请求

  • map()

函数原型:map(func, iterable[, chunksize=None])

Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。

注意:

虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程

map返回的是一个列表,由func函数的返回值组成

  • close()

关闭进程池(Pool),使其不再接受新的任务。

  • terminate()

立刻结束工作进程,不再处理未处理的任务

  • join()

使主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。

  • multiprocessing.cpu_count()

获取CPU的核数

>>> import multiprocessing
>>> multiprocessing.cpu_count()
8
#coding: utf-8
import multiprocessing
import os
import time
import random

def m1(x):
	time.sleep(random.random()*4)
	print("pid:",os.getpid(),x*x)
	return x*x
	
if __name__ == '__main__':
	pool = multiprocessing.Pool(multiprocessing.cpu_count())
	i_list = range(10)
	print(pool.map(m1,i_list))

示例1:apply_async()+map()

#encoding=utf-8
from multiprocessing import Pool
import multiprocessing
import time

def f(x):
	print(multiprocessing.current_process().name,x)
	time.sleep(1)
	return x * x

if __name__ == '__main__':
	pool = Pool(processes = 4) # start 4 worker processes
	result = pool.apply_async(f, [10]) # evaluate "f(10)" asynchronously,从进程池中找一个进程去执行任务
	result2 = pool.apply_async(f, [11])
	print(result.get(timeout = 2))# 超时时间设置为2秒,2秒内进程还没有返回结果就抛出异常
	print(result2.get(timeout = 2))
	print(pool.map(f, range(10))) # prints "[0, 1, 4,..., 81]"

示例2:实现给map()传递多个参数

#encoding=utf-8
import multiprocessing
from multiprocessing import Pool

def f(object):
	print(multiprocessing.current_process().name,object.x * object.y)
	return object.x * object.y

class A:
	def __init__(self,a,b):
		self.x = a
		self.y = b

if __name__=='__main__':
	pool = Pool(multiprocessing.cpu_count())
	params = [A(i,i) for i in range(10)]
	print(pool.map(f,params))

 

示例3:多线程与单线程执行时间比较 

#encoding=utf-8
import time
from multiprocessing import Pool

def run(n):
	time.sleep(1)
	return n * n
	
if __name__=='__main__':
	testFL = [1,2,3,4,5,6]
	print("Single process execution sequence:")#顺序执行(也就是串行执行,单进程)
	s = time.time()
	for n in testFL:
		run(n)
	e1 = time.time()
	print("顺序执行时间:",int(e1-s))
	
	print("concurrent:")#创建多个进程,并行执行
	pool = Pool(5)#创建拥有5个进程数量的进程池
	result = pool.map(run,testFL)#result表示全部进程结束后全局的返回结果集
	pool.close()#关闭进程池,不再接受新的任务
	pool.join()#主进程阻塞等待子进程的退出,必须在close()或terminate()之后调用
	e2 = time.time()
	print("并行执行时间:",int(e2-e1))
	print(result)

2.2.3 进程同步:消息队列Queue&JoinableQueue

并发:在一个时间段内,同时做几个任务。1个老师在1个小时内,分别看了5个同学的作业,一个老师干一件事。
并行:在同一个时间点,同时做任务。5个老师,在一个小时内,各自看一个同学的作业,5个老师干5件事。

python:底层实现机制:GIL global interpreter lock 全局解释锁。
产生了大量同步,导致很多操作无法并行,效率很低,无法利用多核cpu

应用场景:
多进程:可以利用多核cpu。计算密集型的程序需要多进程
多线程:因为GIL锁,不可以利用多核cpu。如果是io密集型,使用多线程(也可以使用多进程,多进程会占用更多的资源)
协程:单线程,没有并行。看起来是个并发,不需要实现锁。(更轻量,不能使用多核cpu,占用的资源少)
 

multiprocessing.Queue类似于queue.Queue,一般用来多个进程间交互信息。Queue是进程和线程安全的。它实现了queue.Queue的大部分方法,但task_done()和join()没有实现。
multiprocessing.JoinableQueue是multiprocessing.Queue的子类,增加了task_done()方法和join()方法。
task_done(): 用来告诉queue一个task完成。一般在调用get()时获得一个task,在task结束后调用task_done()来通知Queue当前task完成。
join(): 阻塞直到queue中的所有的task都被处理(即task_done方法被调用)。

#encoding=utf-8
from multiprocessing import Process, Queue

def offer(queue):
	# 入队列
	queue.put("Hello World")

if __name__ == '__main__':
	# 创建一个队列实例
	q = Queue()#在主进程定义的队列
	p = Process(target = offer, args = (q,))
	p.start()
	print(q.get()) # 出队列,实现了多进程的同步,get依赖于put成功了
	p.join()

示例2:两个子进程间通信Queues通信

#encoding=utf-8
from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
	for value in ['A', 'B', 'C']:
		print('Put %s to queue...' % value)
		q.put(value)
		time.sleep(random.random())

# 读数据进程执行的代码
def read(q):
	time.sleep(1)
	while not q.empty():
	# if not q.empty():
		print('Get %s from queue.' % q.get(True))#True表示死等,可不写
		time.sleep(1) # 目的是等待写队列完成

if __name__=='__main__':
	# 父进程创建Queue,并传给各个子进程
	q = Queue()
	pw = Process(target = write, args = (q,))
	pr = Process(target = read, args = (q,))
	# 启动子进程pw,写入:
	pw.start()
	# 启动子进程pr,读取:
	pr.start()
	# 等待pw结束:
	pw.join()
	pr.join()
	print("Done!")

#encoding=utf-8
import multiprocessing
from multiprocessing import Process, Queue
import os, time, random

# 写数据进程执行的代码:
def write(q):
	for value in ['A', 'B', 'C', 'd', 'e', 'f','g', 'H']:
		print('Put %s to queue...' % value)
		q.put(value)
		time.sleep(random.random())

# 读数据进程执行的代码
def read(q):
	name = multiprocessing.current_process().name
	for i in range(4):
		print('%s Get %s from queue.' % (name,q.get()))

if __name__=='__main__':
	# 父进程创建Queue,并传给各个子进程
	q = Queue()
	pw = Process(target = write, args = (q,))
	pr1 = Process(target = read, args = (q,), name = "pr1")
	pr2 = Process(target = read, args = (q,), name = "pr2")
	# 启动子进程pw,写入:
	pw.start()
	# 启动子进程pr,读取:
	pr1.start()
	pr2.start()
	# 等待pw结束:
	pw.join()
	pr1.join()
	pr2.join()
	print("Done!")

 

 

示例3:Queue&JoinableQueue

multiprocessing.JoinableQueue():取出来的对象是个任务对象(一个函数或者一个对象),你去执行它之后,你需要告诉这个队列,当前任务执行完了,需要调用task_done()方法;如果队列里面的所有任务都被执行了,且每一个任务都调用了task_done()方法,认为此队列的所有任务都被执行完了。最后有几个进程,需要给这个队列里面加入几个None,作为任务结束的标志。

#encoding=utf-8
import multiprocessing
import time

#继承了Process类,要求必须覆盖实现原有的run方法
class Consumer(multiprocessing.Process):
	# 派生进程
	# 2个参数:task_queue任务队列,result_queue存结果的队列
	def __init__(self, task_queue, result_queue):
		multiprocessing.Process.__init__(self)
		self.task_queue = task_queue
		self.result_queue = result_queue
	
	# 重写原进程的run方法
	# 每个进程从tasks的队列取任务取做,都做完了,
	# 取到一个None的时候,跳出while死循环,结束当前进程任务
	def run(self):
		proc_name = self.name #声明了进程的名字
		while True:
			next_task = self.task_queue.get()#从任务队列取任务,Task类的实例
			if next_task is None:
				# Poison pill means shutdown
				print('%s: Exiting' % proc_name)
				self.task_queue.task_done() #任务完成调用task_done()
				break
			print('%s: %s' % (proc_name, next_task))
			answer = next_task() # __call__(),执行任务
			self.task_queue.task_done()
			self.result_queue.put(answer)#把计算结果放到结果队列中
		return

class Task(object):
	def __init__(self, a, b):
		self.a = a
		self.b = b
	
	def __call__(self):
		time.sleep(0.1) # pretend to take some time to do the work
		return '%s * %s = %s' % (self.a, self.b, self.a * self.b)
	
	def __str__(self):
		return '%s * %s' % (self.a, self.b)


if __name__ == '__main__':
	# 新建了一个任务队列
	tasks = multiprocessing.JoinableQueue()
	# 新建了一个存任务结果的跨进程队列
	results = multiprocessing.Queue()
	# 计算了当前cpu的核数
	num_consumers = multiprocessing.cpu_count()
	# 打印有几个消费者:cpu有几核,就有几个消费者
	print('Creating %d consumers' % num_consumers)
	# 创建cup核数个的子进程,一个实例代表一个进程
	consumers = [ Consumer(tasks, results) for i in	range(num_consumers) ]
	# 依次启动子进程
	for w in consumers:
		w.start() #会默认调用Consumer类的run方法
	# Enqueue jobs
	num_jobs = 10
	for i in range(num_jobs):
		tasks.put(Task(i, i))# 在任务队列加入任务,让不同进程去完成任务
	# 有几个进程,放几个None
	# 进程执行的是run方法,里面有死循环,只有遇到None才会跳出死循环结束任务
	for i in range(num_consumers):
		tasks.put(None)
	# 等待所有的任务都被执行完
	tasks.join()
	# 从结果队列中打印任务的执行结果
	while num_jobs:
		result = results.get()
		print ('Result: %s' %result)
		num_jobs -= 1

2.2.4 进程同步:加锁-Lock

锁是为了确保数据一致性,比如读写锁,每个进程给一个变量增加 1,但是如果在一个进程读取但还没有写入的时候,另外的进程也同时读取了,并写入该值,则最后写入的值是错误的,这时候就需要加锁来保持数据一致性。
示例1:不加锁, 相同数字打印无序

#encoding=utf-8
from multiprocessing import Process
import time

def l(num):
	time.sleep(0.2)
	print("Hello Num: %s" % (num))
	time.sleep(0.2)
	print("Hello Num: %s" % (num))

if __name__ == '__main__':
	for num in range(50):
		Process(target = l, args = (num,)).start()

示例2:加锁

#encoding=utf-8
from multiprocessing import Process, Lock
import time

def l(lock, num):
	lock.acquire() # 获得锁
	time.sleep(0.2)
	print("Hello Num: %s" % (num))	
	time.sleep(0.2)
	print("Hello Num: %s" % (num))
	lock.release() # 释放锁

if __name__ == '__main__':
	lock = Lock() # 创建一个共享锁实例
	for num in range(50):
		Process(target = l, args = (lock, num)).start()

 

2.2.5 进程同步:加锁-Semaphore

Semaphore用于控制对共享资源的访问数量。 Semaphore锁和Lock稍有不同, Semaphore
相当于N把锁,获取其中一把就可以执行。
可用锁的总数N在创建实例时传入,比如s = Semaphore(n)。与Lock一样,如果可用锁为0,进程将会阻塞,直到可用锁大于0

#encoding=utf-8
import multiprocessing
import time

def worker(s, i):
	s.acquire()
	print(multiprocessing.current_process().name + "acquire")
	time.sleep(1)
	print(multiprocessing.current_process().name + "release")
	s.release()

if __name__ == "__main__":
	# 设置限制最多3个进程同时访问共享资源
	s = multiprocessing.Semaphore(3)
	for i in range(5):
		p = multiprocessing.Process(target = worker, args = (s, i * 2)).start()

2.2.6  进程同步:信号传递-Event

Event提供一种简单的方法,可以在进程间传递状态信息,实现进程间同步通信。事件可以切换设置和未设置状态。通过使用一个可选的超时值,事件对象的用户可以等待其状态从未设置变为设置。

#encoding=utf-8
import multiprocessing
import time

def wait_for_event(e):
	"""Wait for the event to be set before doing anything"""
	print('wait_for_event: starting')
	e.wait() # 等待收到能执行信号,如果一直未收到将一直阻塞
	print('wait_for_event: e.is_set()->', e.is_set())

def wait_for_event_timeout(e, t):
	"""Wait t seconds and then timeout"""
	print('wait_for_event_timeout: starting')
	e.wait(t)# 等待t秒超时,此时Event的状态仍未未设置,继续执行
	print('wait_for_event_timeout: e.is_set()->', e.is_set())
	e.set()# 初始内部标志为真

if __name__ == '__main__':
	e = multiprocessing.Event()
	print("begin,e.is_set()", e.is_set())
	w1 = multiprocessing.Process(name='block', target=wait_for_event, args=(e,))
	w1.start()
	
	#可将2改为5,看看执行结果
	w2 = multiprocessing.Process(name='nonblock', target=wait_for_event_timeout, args=(e, 2))
	w2.start()
	print('main: waiting before calling Event.set()')
	time.sleep(3)
	# e.set() #可注释此句话看效果
	print('main: event is set')

 将2改成5,并取消注释e.set()后

2.2.7 进程同步:使用管道-Pipe

Pipe是两个进程间通信的工具。Pipe可以是单向(half-duplex),也可以是双向(duplex)。通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。一个进程从Pipe一端输入对象,然后被Pipe另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。

Pipe的每个端口同时最多一个进程读写,否则会出现各种问题,可能造成corruption异常。Pipe对象建立的时候,返回一个含有两个元素元组对象,每个元素代表Pipe的一端(Connection对象)。我们对Pipe的某一端调用send()方法来传送对象,在另一端使用recv()来接收。

#encoding=utf-8
import multiprocessing as mp

def proc_1(pipe):
	pipe.send('hello')
	print('proc_1 received: %s' %pipe.recv())
	pipe.send("what is your name?")
	print('proc_1 received: %s' %pipe.recv())

def proc_2(pipe):
	print('proc_2 received: %s' %pipe.recv())
	pipe.send('hello, too')
	print('proc_2 received: %s' %pipe.recv())
	pipe.send("I don't tell you!")

if __name__ == '__main__':
	# 创建一个管道对象pipe
	pipe = mp.Pipe()
	print(len(pipe))
	print(type(pipe))
	# 将第一个pipe对象传给进程1
	p1 = mp.Process(target = proc_1, args =	(pipe[0], ))
	# 将第二个pipe对象传给进程2
	p2 = mp.Process(target = proc_2, args =	(pipe[1], ))
	p2.start()
	p1.start()
	p2.join()
	p1.join()

2.2.8 进程同步:使用Condition

一个condition变量总是与某些类型的锁相联系,当几个condition变量必须共享同一个锁的时候,是很有用的。锁是conditon对象的一部分:没有必要分别跟踪。

condition变量服从上下文管理协议:with语句块封闭之前可以获取与锁的联系。 acquire() 和release() 会调用与锁相关联的相应方法。

wait()方法会释放锁,当另外一个进程使用notify() or notify_all()唤醒它之前会一直阻塞。一旦被唤醒,wait()会重新获得锁并返回,Condition类实现了一个conditon变量。这个condition变量允许一个多个进程等待,直到他们被另一个进程通知。如果lock参数,被给定一个非空的值,那么他必须是一个lock或者Rlock对象,它用来做底层锁。否则,会创建一个新的Rlock对象,用来做底层锁。

wait(timeout=None) :等待通知,或者等到设定的超时时间。当调用wait()方法时,如果调用它的进程没有得到锁,那么会抛出一个RuntimeError异常。 wait()释放锁以后,在被调用相同条件的另一个进程用notify() or notify_all() 叫醒之前会一直阻塞。如果有等待的进程,notify()方法会唤醒一个在等待conditon变量的进程。notify_all() 则会唤醒所有在等待conditon变量的进程。

注意:

notify()和notify_all()不会释放锁,也就是说,进程被唤醒后不会立刻返回他们的wait() 调用。除非进程调用notify()和notify_all()之后放弃了锁的所有权。在典型的设计风格里,利用condition变量加锁去允许访问一些共享状态,进程在获取到它想得到的状态前,会反复调用wait()。修改状态的进程在他们状态改变时调用 notify() or notify_all(),用这种方式,进程会尽可能的获取到想要的一个等待者状态。

#encoding=utf-8
import multiprocessing as mp
import threading
import time

def consumer(cond):
	with cond:
		print("consumer before wait")
		cond.wait() # 等待消费
		print("consumer after wait")

def producer(cond):
	with cond:
		print("producer before notifyAll")
		cond.notify_all() # 通知消费者可以消费了
		print("producer after notifyAll")

if __name__ == '__main__':
	condition = mp.Condition()
	p1 = mp.Process(name = "p1", target = consumer,	args=(condition,))
	p2 = mp.Process(name = "p2", target = consumer,	args=(condition,))
	p3 = mp.Process(name = "p3", target = producer,	args=(condition,))
	p1.start()
	time.sleep(2)
	p2.start()
	time.sleep(2)
	p3.start()

2.2.9 进程同步: 共享变量(数字/字符串/列表/字典/实例对象)

程序运行中生成的变量是放在进程的数据区中,每个进程都是独立的地址空间,所以用一般的方法是不能共享变量的,multiprocessing模块提供了Array/Manager/Value类,借助以上类能够实现进程间共享数字变量/字符串变量/列表/字典/实例对象。

  • Value类

直接类似Value('d', 0.0)即可

构造方法:multiprocessing.Value(typecode_or_type, *args[, lock])。

函数作用返回从共享内存中分配的一个ctypes 对象,其中typecode_or_type定义了返回的类型。它要么是一个ctypes类型,要么是一个代表ctypes类型的code。比如c_bool和'b'是同样的,因为'b'是c_bool的code。

参数说明

ctypes是Python的一个外部函数库,它提供了和C语言兼任的数据类型,可以调用DLLs或者共享库的函数,能被用作在python中包裹这些库。对于共享整数或者单个字符,初始化比较简单,参照下图映射关系即可:

 如果共享的是字符串,则在上表是找不到映射关系的,就是没有code可用。需要使用原始的ctype类型,例如 

from ctypes import c_char_p
ss = Value(c_char_p, 'hello')

ctype类型可从下表查阅

  *args是传递给ctypes的构造参数

  • Array类:

构造方法:Array(typecode_or_type, size_or_initializer, *, lock=True)

函数作用:返回从共享内存中分配的一个数据内容为ctypes 类型的数组ctypes 类型参见上表

参数说明:size_or_initializer可以是一个初始化好的列表也可以是列表的大小,如果是列表大小的话,默认值和c中对应的数据类型是一致的。

  • Manager类:

Manager对象的使用方法同multiprocessing中的类似,共享的字符串只能用manager.Value()来实现,因为multiprocessing.Value()的参数不支持字符串

>>> from multiprocessing import Manager
>>> manager = Manager()
>>> dir(manager)
['Array', 'Barrier', 'BoundedSemaphore', 'Condition', 'Event', 'JoinableQueue', 'Lock', 'Namespace', 'Pool', 'Queue', 'RLock', 'Semaphore', 'Value', '_Client', '_Listener', '_Server', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__enter__', '__eq__', '__exit__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_address', '_authkey', '_create', '_ctx', '_debug_info', '_finalize_manager', '_number_of_objects', '_process', '_registry', '_run_server', '_serializer', '_state', 'address', 'connect', 'dict', 'get_server', 'join', 'list', 'register', 'shutdown', 'start']

示例1:未使用共享变量

#encoding=utf-8
from multiprocessing import Process

def f(n, a):
	n = 3.1415927
	print("子进程中变量n:",n)
	for i in range(len(a)):
		a[i] = -a[i]
		print("子进程中list元素:",a[i])

if __name__ == '__main__':
	num = 0 #
	arr = list(range(10))
	p = Process(target = f, args = (num, arr))
	p.start()
	p.join()
	print(num)
	print(arr[:])

示例2:使用共享变量

#encoding=utf-8
from multiprocessing import Process, Value, Array

def f(n, a):
	n.value = 3.1415927
	print("子进程中变量n:",n)
	for i in range(len(a)):
		a[i] = -a[i]
		print("子进程中list元素:",a[i])

if __name__ == '__main__':
	num = Value('d', 0.0) # 创建一个进程间共享的数字类型,默认值为0
	arr = Array('i', range(10)) # 创建一个进程间共享的数组类型,初始值为range[10]
	p = Process(target = f, args = (num, arr))
	p.start()
	p.join()
	print(num.value) # 获取共享变量num的值
	print(arr[:])

示例3:共享变量+锁

#encoding=utf-8
import time
from multiprocessing import Process, Value, Lock

class Counter(object):
	def __init__(self, initval = 0):
		self.val = Value('i', initval)
		self.lock = Lock()

	def increment(self):
		with self.lock:
			self.val.value += 1 # 共享变量自加1
			#print(“increment one time!”,self.value())#加此句死锁

	def value(self):
		with self.lock:
			return self.val.value

def func(counter):
	for i in range(50):
		time.sleep(0.01)
		counter.increment()

if __name__ == '__main__':
	counter = Counter()
	procs = [Process(target = func, args =	(counter,)) for i in range(10)]
	# 等价于
	# for i in range(10):
		# Process(target = func, args = (counter,))
	for p in procs: p.start()
	for p in procs: p.join()
	print(counter.value()) # 输出:500

示例4:共享字符串变量

#encoding=utf-8
from multiprocessing import Process, Manager, Value
from ctypes import c_char_p

def greet(shareStr):
	shareStr.value = shareStr.value + ", World!"

if __name__ == '__main__':
	manager = Manager()
	shareStr = manager.Value(c_char_p, "Hello")
	process = Process(target = greet, args = (shareStr,))
	process.start()
	process.join()
	print(shareStr.value) # 输出:Hello, World!

示例5:共享list、dict

#encoding=utf-8
from multiprocessing import Process, Manager

def f( shareDict, shareList ):
	shareDict[1] = '1'
	shareDict['2'] = 2
	shareDict[0.25] = None
	shareList.reverse() # 翻转列表

if __name__ == '__main__':
	manager = Manager()
	shareDict = manager.dict() # 创建共享的字典类型
	shareList = manager.list(range(10)) # 创建共享的列表类型
	p = Process(target = f, args = (shareDict, shareList))
	p.start()
	p.join()
	print(shareDict)# 输出:{1: '1', '2': 2, 0.25: None}
	print(shareList)#输出:[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]

Manager()函数返回一个管理对象,它控制了一个服务端进程,用来保持Python对象,并允许其它进程使用代理来管理这些对象。
Manager()返回的管理者,支持类型包括:list,dict, Namespace, Lock, RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value and Array。
Manager比使用共享内存对象更灵活,因为它支持任意对象类型。同样的,单个的manager可以通过网络在不同机器上进程间共享。但是,会比共享内存慢。

示例6:共享队列

#encoding=utf-8
from multiprocessing import Pool,Manager

def func(q):
	print("*"*10)
	q.put("12346")

if __name__ == "__main__":
	manager = Manager()
	q = manager.Queue()
	pool = Pool(processes=4)
	for i in range(5):
		pool.apply_async(func,(q,))
	pool.close()
	pool.join()
	print(q.qsize()) #输出:5
#encoding=utf-8
import time
from multiprocessing import Pool,Queue,Process

def func(q):
	print('*'*30)
	q.put('1111')

if __name__ == "__main__":
	queue = Queue()
	pool = Pool(4)#生成一个容量为4的进程池
	for i in range(5):
		pool.apply_async(func, (queue,))#向进程池提交目标请求
	pool.close()
	pool.join()
	print(queue.qsize())#输出:0,也没打印星号

请注意:上例中的Queue无法在进程池中实现共享,在Process类生成的进程间可以共享。 

示例7:共享实例对象

#encoding=utf-8
import time, os
import random
from multiprocessing import Pool, Value, Lock
from multiprocessing.managers import BaseManager

class MyManager(BaseManager):
	pass

def Manager():
	m = MyManager() #生成了一个MyManager的实例
	m.start()
	return m

class Counter(object):#要在不同进程中共享的实例的类
	def __init__(self, initval=0):
		self.val = Value('i', initval)
		self.lock = Lock()
	
	def increment(self):
		with self.lock:
			self.val.value += 1
	
	def value(self):
		with self.lock:
			return self.val.value

#将Counter类注册到Manager管理类中
MyManager.register('Counter', Counter)

def long_time_task(name,counter):
	time.sleep(0.2)
	print('Run task %s (%s)...\n' % (name, os.getpid()))
	start = time.time()
	#time.sleep(random.random() * 3)
	for i in range(50):
		time.sleep(0.01)
		counter.increment()
	end = time.time()
	print('Task %s runs %0.2f seconds.' % (name, (end - start)))

if __name__ == '__main__':
	manager = Manager()#调用Manager函数,获取MyManager的实例
	# 创建共享Counter类实例对象的变量, Counter类的初始值0
	counter = manager.Counter(0)
	print('Parent process %s.' % os.getpid())
	p = Pool()
	for i in range(5):
		p.apply_async(long_time_task, args = (str(i), counter))
	print('Waiting for all subprocesses done...')
	p.close()
	p.join()
	print('All subprocesses done.')
	print(counter.value())

以上例子实现了,一个类实例在不同进程中的共享
使用场景:将很多变量存储到类实例中,然后共享这个类实例,就可以实现大批变量在不同进程中的共享了。

 使用进程池作为多并发执行程序模板:

#coding=utf-8
__author__ = 'Parmley'
from multiprocessing import Pool, Value, Lock, Manager
import os, time, random

def long_time_task(name,requestCount,countList):
	requestCount.value = requestCount.value + 1
	print("计数: ", requestCount.value)
	countList.append(requestCount.value)
	time.sleep(0.2)
	print('Run task %s (%s)...\n' % (name, os.getpid()))
	start = time.time()
	time.sleep(random.random() * 3)
	end = time.time()
	print('Task %s runs %0.2f seconds.' % (name, (end - start)))


if __name__ == '__main__':
	manager = Manager()
	requestCount = manager.Value('i',0)
	countList = manager.list([])
	print('Parent process %s.' % os.getpid())
	p = Pool()
	for i in range(5):
		p.apply_async(long_time_task, args = (str(i),requestCount,countList))
	print('Waiting for all subprocesses done...')
	p.close()
	p.join()
	print('All subprocesses done.')
	print(requestCount.value)
	print(countList)

2.2.10 进程日志

#encoding=utf-8
import multiprocessing
import logging
import sys

def worker():
	print('I am working....')
	sys.stdout.flush()

if __name__ == '__main__':
	# 设置日志输出到控制台
	multiprocessing.log_to_stderr()
	logger = multiprocessing.get_logger()
	# 设置输出日志的级别
	logger.setLevel(logging.INFO)
	p = multiprocessing.Process(target = worker)
	p.start()
	p.join()

2.2.11 守护进程 

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件;守护进程是不阻挡主进程退出,会随着主进程退出而退出,如果要等待守护进程退出,需要加上join函数。

非守护进程:主进程结束了,它还可以继续执行,直到结束。

#encoding=utf-8
import multiprocessing
import time, logging
import sys

def daemon():
	p = multiprocessing.current_process()
	print('Starting:', p.name, p.pid)
	sys.stdout.flush() # 将缓冲区数据写入终端
	time.sleep(2)
	print('Exiting :', p.name, p.pid)
	sys.stdout.flush()

def non_daemon():
	p = multiprocessing.current_process()
	print('Starting:', p.name, p.pid)
	sys.stdout.flush()
	print('Exiting :', p.name, p.pid)
	sys.stdout.flush()
	
if __name__ == '__main__':
	# 设置日志输出到控制台
	multiprocessing.log_to_stderr()
	logger = multiprocessing.get_logger()
	# 设置输出日志的级别
	logger.setLevel(logging.DEBUG)
	d = multiprocessing.Process(name='daemon', target=daemon)
	d.daemon = True #设定它为守护进程
	n = multiprocessing.Process(name='non-daemon', target=non_daemon)
	n.daemon = False #设定它为非守护进程
	d.start()
	time.sleep(1)
	n.start()
	# d.join(1)
	# n.join()
	print('d.is_alive()', d.is_alive())
	print("n.is_alive()", n.is_alive())
	print("main Process end!")

 2.3 subprocess模块

使用subprocess这个模块来产生子进程,并连接到子进程的标准输入/输出/错误中去,还可以得到子进程的返回值。官方的解释:

This module allows you to spawn processes, connect to their input/output/error pipes, and obtain their return codes.

即允许你去创建一个新的进程让其执行另外的程序并与它进行通信,获取标准的输入、标准输出、标准错误以及返回码等。

另外subprocess还提供了一些管理标准流(standard stream)和管道(pipe)的工具,从而在进程间使用文本通信。

2.3.1 subprocess模块中的常用函数

函数

描述

subprocess.run()

Python 3.5中新增的函数。执行指定的命令,等待命令执行完成后返回一个包含执行结果的CompletedProcess类的实例。

subprocess.call()

执行指定的命令,返回命令执行状态,其功能类似于os.system(cmd)。

subprocess.check_call()

Python 2.5中新增的函数。 执行指定的命令,如果执行成功则返回状态码,否则抛出异常。其功能等价于subprocess.run(..., check=True)。

subprocess.check_output()

Python 2.7中新增的的函数。执行指定的命令,如果执行状态码为0则返回命令执行结果,否则抛出异常。

subprocess.getoutput(cmd)

接收字符串格式的命令,执行命令并返回执行结果,其功能类似于os.popen(cmd).read()和commands.getoutput(cmd)。

subprocess.getstatusoutput(cmd)

执行cmd命令,返回一个元组(命令执行状态, 命令执行结果输出),其功能类似于commands.getstatusoutput()。

 说明:

  1. 在Python 3.5之后的版本中,官方文档中提倡通过subprocess.run()函数替代其他函数来使用subproccess模块的功能;
  2. 在Python 3.5之前的版本中,我们可以通过subprocess.call(),subprocess.getoutput()等上面列出的其他函数来使用subprocess模块的功能;
  3. subprocess.run()、subprocess.call()、subprocess.check_call()和ubprocess.check_output()都是通过对subprocess.Popen的封装来实现的高级函数,因此如果我们需要更复杂功能时,可以通过subprocess.Popen来完成。
  4. subprocess.getoutput()和subprocess.getstatusoutput()函数是来自Python 2.x的commands模块的两个遗留函数。它们隐式的调用系统shell,并且不保证其他函数所具有的安全性和异常处理的一致性。另外,它们从Python 3.3.4开始才支持Windows平台

2.3.2 各函数的定义及参数说明

函数参数列表:

subprocess.run(args, *, stdin=None, input=None, stdout=None, stderr=None, shell=False, timeout=None, check=False, universal_newlines=False)

subprocess.call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

subprocess.check_call(args, *, stdin=None, stdout=None, stderr=None, shell=False, timeout=None)

subprocess.check_output(args,*,stdin=None,stderr=None,shell=False,universal_newlines=False, timeout=None)

subprocess.getstatusoutput(cmd)

subprocess.getoutput(cmd)

参数说明:

  • args: 要执行的shell命令,默认应该是一个字符串序列,如['df', '-Th']或('df', '-Th'),也可以是一个字符串,如'df -Th',但是此时需要把shell参数的值置为True。
  • shell: 如果shell为True,那么指定的命令将通过shell执行。如果我们需要访问某些shell的特性,如管道、文件名通配符、环境变量扩展功能,这将是非常有用的。当然,python本身也提供了许多类似shell的特性的实现,如glob、fnmatch、os.walk()、os.path.expandvars()、os.expanduser()和shutil等。
  • check: 如果check参数的值是True,且执行命令的进程以非0状态码退出,则会抛出一个CalledProcessError的异常,且该异常对象会包含 参数、退出状态码、以及stdout和stderr(如果它们有被捕获的话)。
  • stdout, stderr:

        run()函数默认不会捕获命令执行结果的正常输出和错误输出,如果我们向获取这些内容需要传递subprocess.PIPE,然后可以通过返回的CompletedProcess类实例的stdout和stderr属性或捕获相应的内容;
call()和check_call()函数返回的是命令执行的状态码,而不是CompletedProcess类实例,所以对于它们而言,stdout和stderr不适合赋值为subprocess.PIPE;
check_output()函数默认就会返回命令执行结果,所以不用设置stdout的值,如果我们希望在结果中捕获错误信息,可以执行stderr=subprocess.STDOUT

  • input: 该参数是传递给Popen.communicate(),通常该参数的值必须是一个字节序列,如果universal_newlines=True,则其值应该是一个字符串。
  • universal_newlines: 该参数影响的是输入与输出的数据格式,比如它的值默认为False,此时stdout和stderr的输出是字节序列;当该参数的值设置为True时,stdout和stderr的输出是字符串

2.3.3 subprocess.Popen类

subprocess模块用subprocess.Popen类来创建进程,并与进程进行复杂的交互。

其构造函数如下:

__init__(self, args, bufsize=0, executable=None, stdin=None, stdout=None,stderr=None,preexec_fn=None,close_fds=False,shell=False,cwd=None,

env=None,universal_newlines=False,startupinfo=None, creationflags=0)

➢ 主要参数说明:
args
args should be a string, or a sequence of program arguments.也就是说必须是一个字符串或者序列类型(如:字符串、 list、元组),用于指定进程的可执行文件及其参数。如果是一个序列类型参数,则序列的第一个元素通常都必须是一个可执行文件的路径。当然也可以使用executeable参数来指定可执行文件的路径。 例如: [‘ls’, ’ -l’] ,shell=False时使用。
bufsize:
如果指定了bufsize参数作用就和内建函数open()一样: 0表示不缓冲, 1表示行缓冲,其他正数表示近似的缓冲区字节数,负数表示使用系统默认值。默认是0。
stdin,stdout,stderr:
分别表示程序的标准输入、标准输出、标准错误。有效的值可以是PIPE,存在的文件描述符,存在的文件对象或None,如果为None需从父进程继承过来; stdout可以是PIPE,表示对子进程创建一个管道;stderr可以是STDOUT,表示标准错误数据应该从应用程序中捕获并作为标准输出流stdout的文件句柄。
shell:
默认为False,如果这个参数被设置为True,程序将通过shell来执行。
env:
它描述的是子进程的环境变量。如果为None,子进程的环境变量将从父进程继承而来。
创建Popen类的实例对象

res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.STDOUT)

cmd:向子进程传入需要执行的shell命令,如: ls –al (在shell=True的时候使用)
subprocess.PIPE:在创建Popen对象时,subprocess.PIPE可以初始化为stdin,stdout或stderr的参数,表示与子进程通信的标准输入流,标准输出流以及标准错误,将多个子进程的输入和输出连接在一起,构成管道(pipe)
subprocess.PIPE实际上为文本流提供一个缓存区,a子进程的标准输出设定为pipe后,可以作为b子进程的标准输入。
subprocess.STDOUT: 作为Popen对象的stderr的参数,表示将标准错误通过标准输出流输出

示例1:创建一个subprocess目录

>>> import subprocess
>>> a = subprocess.Popen('mkdir subprocesstest',shell=True)

示例2:用python执行几个命令

import subprocess
#默认参数shell=Fasle,必须使用列表方式来设定命令和参数,例如:[命令,参数1,参数2]
obj = subprocess.Popen(["python"],stdin=subprocess.PIPE, stdout=subprocess.PIPE,stderr=subprocess.PIPE)
obj.stdin.write(b"print(1);\n")
obj.stdin.write(b"print(2);\n")
obj.stdin.write(b"print(3);\n")
obj.stdin.write(b"print(4);\n")
obj.stdin.close()
cmd_out = obj.stdout.read()
obj.stdout.close()
cmd_error = obj.stderr.read()
obj.stderr.close()
print(cmd_out)#打印:b'1\r\n2\r\n3\r\n4\r\n'
print(cmd_error)#打印:b''

第二种写法

import subprocess
obj = subprocess.Popen(["python"], stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
obj.stdin.write(b"print(1)\n")
obj.stdin.write(b"print(2)\n")
obj.stdin.write(b"print(3)\n")
obj.stdin.write(b"print(4)\n")
out_info,out_error = obj.communicate()
print(out_info,out_error)#打印:b'1\r\n2\r\n3\r\n4\r\n' b''

关于communicate()方法的说明:

  • 该方法中的可选参数 input 应该是将被发送给子进程的数据,或者如没有数据发送给子进程,该参数应该是None。input参数的数据类型必须是字节串,如果universal_newlines参数值为True,则input参数的数据类型必须是字符串。
  • 该方法返回一个元组(stdout_data, stderr_data),这些数据将会是字节串或字符串(如果universal_newlines的值为True)。
  • 如果在timeout指定的秒数后该进程还没有结束,将会抛出一个TimeoutExpired异常。捕获这个异常,然后重新尝试通信不会丢失任何输出的数据。但是超时之后子进程并没有被杀死,为了合理的清除相应的内容,一个好的应用应该手动杀死这个子进程来结束通信。
  • 需要注意的是,这里读取的数据是缓冲在内存中的,所以,如果数据大小非常大或者是无限的,就不应该使用这个方法。

 第三种写法(在Linux上运行)

import subprocess
child1 = subprocess.Popen(["cat","/etc/passwd"], stdout=subprocess.PIPE)
child2 = subprocess.Popen(["grep","0:0"],stdin=child1.stdout, stdout=subprocess.PIPE)# 把上一个进程的输出作为下一个进程的输入信息
out = child2.communicate()
print(out)

示例3:subprocess进程通信实例

打开一个只有ip地址的文本文件,读取其中的ip或域名,然后进行ping操作,并将ping结果写入ping.txt文件中。

import subprocess
import os

class Shell(object) :
	def runCmd(self, cmd) :
		res = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,		stderr=subprocess.STDOUT)
		# 获取子进程的标准输出,标准错误信息
		sout, serr = res.communicate()
		#sout:执行命令后的输出内容,serr出错内容,res.pid为进程编号
		return res.returncode, sout, serr, res.pid
		
shell = Shell()
fp = open('ip.txt', 'r')
ipList = fp.readlines()
fp.close()
print(ipList)
fp = open('ping.txt', 'a')
for i in ipList :
	i = i.strip()
	result = shell.runCmd('ping ' + i)
	if result[0] == 0 :#表示ping成功
		w = i + ' : 0'
		fp.write(w + '\n')
	else :
		w = i + ' : 1'
		fp.write(w + '\n')
	print(result[1].decode("gbk"))#打印输出信息
fp.close()

实例4:把输出结果放到文件中

import os
import subprocess

class Shell:
	def runCmd(self, cmd):
		fp = open("demo31.txt","w")
		res = subprocess.Popen(cmd, shell=True, stdout=fp, stderr=subprocess.STDOUT)
		
Shell().runCmd("dir")

2.3.4 subprocess.call()

import os
import subprocess

class Shell:
	def runCmd(self, cmd):
		res = subprocess.call(cmd, shell=True, stderr=subprocess.STDOUT)
		print(res)
		
Shell().runCmd("dir")

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值