multiprocessing模块是Python提供的用于多进程开发的包,multiprocessing包提供本地和远程两种并发,通过使用子进程而非线程有效地回避了全局解释器锁。
(一)创建进程Process 类
创建进程的类,其源码在multiprocessing包的process.py里,有兴趣的可以对照着源码边理解边学习。它的用法同threading.Thread差不多,从它的类定义上就可以看的出来,如下:
class Process(object):
'''
Process objects represent activity that is run in a separate process
The class is analagous to `threading.Thread`
'''
_Popen = None
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
assert group is None, 'group argument must be None for now'
count = _current_process._counter.next()
self._identity = _current_process._identity + (count,)
self._authkey = _current_process._authkey
self._daemonic = _current_process._daemonic
self._tempdir = _current_process._tempdir
self._parent_pid = os.getpid()
self._popen = None
self._target = target
self._args = tuple(args)
self._kwargs = dict(kwargs)
self._name = name or type(self).__name__ + '-' + \
':'.join(str(i) for i in self._identity)
Process([group [, target [, name [, args [, kwargs]]]]])
group实质上不使用,是保留项,便于以后扩展。
target表示调用对象,
args表示调用对象的位置参数元组
kwargs表示调用对象的字典
name为别名,即进程的名字
它的方法/属性跟threading.Thread也有很多类似的地方,主要有:
start():开始进程活动。
run():表示进程的活动方法,可以在子类中覆盖它。
join([timeout]):是用来阻塞当前上下文,直至该进程运行结束,一个进程可以被join()多次,timeout单位是秒。
terminate():结束进程。在Unix上使用的是SIGTERM,在Windows平台上使用TerminateProcess
is_alive():判断进程是否还活着。
name:一个字符串,表示进程的名字,也可以通过赋值语句利用它来修改进程的名字
ident:进程的ID,如果进程没开始,结果是None
pid:同ident,大家可以看看ident和pid的实现,是利用了os模块的getpid()方法。
authkey:设置/获取进程的授权密码。当初始化多进程时,使用os.urandom()为主进程分配一个随机字符串。当创建一个Process对象时,它将继承其父进程的认证密钥, 但是可以通过将authkey设置为另一个字节字符串来改变。这里authkey为什么既可以设置授权密码又可以获取呢?那是因为它的定义使用了property装饰器,源码如下:
@property
def authkey(self):
return self._authkey
@authkey.setter
def authkey(self, authkey):
'''
Set authorization key of process
'''
self._authkey = AuthenticationString(authkey)
这是property的一个高级用法,如果理解了其实也很简单,有兴趣的去查看其它资料。
daemon:一个布尔值,指示进程是(True)否(False)是一个守护进程。它必须在调用start()之前设置,否则会引发RuntimeError。它的初始值继承自创建它的进程;进程不是一个守护进程,所以在进程中创建的所有进程默认daemon = False。
exitcode:返回进程退出时的代码。进程运行时其值为None,如果为–N,表示被信号N结束。
(1)一个简单的单进程例子
#coding=utf-8
import multiprocessing
import datetime
import time
def worker(interval):
n = 5
while n > 0:
print "The now is %s"% datetime.datetime.now()
time.sleep(interval)
n -= 1
if __name__ == "__main__":
p = multiprocessing.Process(target = worker, args = (3,))
p.start()#开始进程
#p.terminate()#结束进程
#p.join(9)#阻塞当前上下文
print "p.authkey",p.authkey#获取进程的授权密码
p.authkey = u"123"#设置进程的授权密码
print "p.authkey", p.authkey#获取进程的授权密码
print "p.pid:", p.pid,p.ident#进程ID
p.name = 'helloworld'#修改进程名字
print "p.name:", p.name#进程名字
print "p.is_alive:", p.is_alive()#是否是活的
运行结果如下图:
上面的代码有两行注释掉的,大家可以把注释去掉,体会、理解这两个方法的用处,在此不贴我的运行结果了。
(2)自定义进程类,并开启多个进程
import multiprocessing
import datetime
import time
class MyProcess(multiprocessing.Process):
"""
自定义进程类
"""
def __init__(self,interval,group=None,target=None,name=None,args=(),kwargs={}):
multiprocessing.Process.__init__(self,group,target,name,args,kwargs=kwargs)
self.interval = interval
def run(self):
n = 5
while n > 0:
print("the time is %s"%datetime.datetime.now())
time.sleep(self.interval)
n -= 1
def worker_1(interval):
print "worker_1"
time.sleep(interval)
print "end worker_1"
def worker_2(interval):
print "worker_2"
time.sleep(interval)
print "end worker_2"
def worker_3(interval):
print "worker_3"
time.sleep(interval)
print "end worker_3"
if __name__ == "__main__":
p1 = MyProcess(interval=2,target = worker_1, args = (2,))
p2 = MyProcess(interval=2,target = worker_2, args = (3,))
p3 = MyProcess(interval=2,target = worker_3, args = (4,))
p1.start()
p2.start()
p3.start()
print "current process",multiprocessing.current_process(),multiprocessing.active_children()
print("The number of CPU is:" + str(multiprocessing.cpu_count()))
for p in multiprocessing.active_children():
print("child p.name:" + p.name + "\tp.id" + str(p.pid))
print "END!!!!!!!!!!!!!!!!!"
运行结果如下:
看看打印出来的时间,三个进程应该是并行执行的。
(二)进程间通信
multiprocessing模块支持两种进程间的通信方式:Queue(队列)和Pipe(管道)。
(1)Queue
multiprocessing中的Queue类的定义在queues.py文件里。和Queue.Queue差不多,multiprocessing中的Queue类实现了Queue.Queue的大部分方法(可以参考上篇博文Python:线程、进程与协程(3)——Queue模块及源码分析),但task_done()和join()没有实现,主要方法和属性有
qsize():返回Queue的大小
empty():返回一个布尔值,表示Queue是否为空
full():返回一个布尔值,表示Queue是否满
put(item[, block[, timeout]]):向队列里添加元素item,block设置为False的时候,如果队列满了则抛出Full异常。如果block设置为True,timeout设置为None时,则会一种等到有空位的时候再添加进队列;否则会根据timeout设定的超时值抛出Full异常。
put_nowait(item):等价与put(item,False)。
get([block[, timeout]]):从队列中删除元素并返回该元素的值,如果timeout是一个正数,它会阻塞最多超时秒数,并且如果在该时间内没有可用的项目,则引发Empty异常。
get_nowait():等价于get(False)
close():表示该Queue不在加入新的元素
join_thread():加入后台线程。这只能在调用close()之后使用。它阻塞直到后台线程退出,确保缓冲区中的所有数据都已刷新到管道。默认情况下,如果进程不是队列的创建者,则退出, 它将尝试加入队列的后台线程。 该进程可以调用cancel_join_thread()来做
cancel_join_thread():在阻塞中阻止join_thread(),防止后台线程在进程退出时被自动连接 ,肯能会导致数据丢失。
(2)Pipe
Pipe不是类,是函数,该函数定义在 multiprocessing中的connection.py里,函数原型Pipe(duplex=True),
返回一对通过管道连接的连接对象conn1和conn2。
如果duplex是True(默认值),则管道是双向的。
如果duplex是False,则管道是单向的:conn1只能用于接收消息,conn2只能用于发送消息。
Pipe()返回的两个连接对象表示管道的两端,每个连接对象都有send()和recv()方法(还有其它方法),分别是发送和接受消息。下面举个简单的例子,一个发送数据,一个接受数据
#coding=utf-8
import multiprocessing
import time
def proc1(pipe):
"""
发送数据
"""
while True:
for i in xrange(100):
print "send: %s" %(i)
pipe.send(i)#发送数据
time.sleep(1)
def proc2(pipe):
"""
接收数据
"""
while True:
print "proc2 rev:", pipe.recv()#接受数据
time.sleep(1)
if __name__ == "__main__":
pipe1,pipe2 = multiprocessing.Pipe()#返回两个连接对象
p1 = multiprocessing.Process(target=proc1, args=(pipe1,))
p2 = multiprocessing.Process(target=proc2, args=(pipe2,))
p1.start()
p2.start()
p1.join()
p2.join()
运行结果如下:
(三)进程间的同步
multiprocessing包含与threading中所有同步原语等同的原语,它也有Lock,RLock,Even,Condition,Semaphore,BoundedSemaphore。用法都差不多,它们的定义在 multiprocessing包的synchronize.py文件里,在此不过多介绍,有兴趣的可以参考Python:线程、进程与协程(2)——threading模块里相关的概念理解。如果理解了相关概念,在 multiprocessing模块中使用是一样的,看下面这个简单的例子吧,有两个进程要向某个文件中写入内容,为了避免访问冲突,可以使用锁。
#coding=utf-8
import multiprocessing
def worker_with(lock, f):
with lock:#Lock等对象也是支持上下文管理器协议的。
fs = open(f, 'a+')
n = 10
while n > 1:
fs.write("Lockd acquired via with\n")
n -= 1
fs.close()
def worker_no_with(lock, f):
lock.acquire()
try:
fs = open(f, 'a+')
n = 10
while n > 1:
fs.write("Lock acquired directly\n")
n -= 1
fs.close()
finally:
lock.release()
if __name__ == "__main__":
lock = multiprocessing.Lock()#定义锁
f = "/home/liulonghua/files.txt"
w = multiprocessing.Process(target = worker_with, args=(lock, f))
nw = multiprocessing.Process(target = worker_no_with, args=(lock, f))
w.start()
nw.start()
print "end"
multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源)。
多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。下篇博文再接着讲进程共享和进程池等。
转载于:https://blog.51cto.com/11026142/1873260