Python的多线程并没有有效利用多个cpu,只用到一个cpu,为了最大的利用cpu资源和提高效率,一般用多进程去实现。
Python多进程常用包时multiprocessing。包含Process、Queue、Pip、Lock、Pool、Manager、Semaphore、Event等类。
下面分别对这些类进行使用和相关用法进行说明:
1、Process
类的构造方式:
def __init__(self, group=None, target=None, name=None, args=(), kwargs={}):
# group:进程所属组,基本不用,可以不传,传的话必须为None
# target:进程调用对象(可以是一个函数名,也可以是一个可调用的对象(实现了__call__方法的类))
# args:调用对象的位置参数元组
# name:给这个进程命名
# kwargs:调用对象的关键字参数字典
实例方法:
is_alive():返回进程是否在运行 ,True或False
start():启动进程,等待CPU调度
join([timeout]):阻塞当前上下文环境,直到调用此方法的进程终止或者到达指定timeout
terminate():不管任务是否完成,立即停止该进程
run():start()调用该方法,当实例进程没有传入target参数,stat()将执行默认的run()方法
属性:
authkey: Set authorization key of process。(没用过)
daemon:守护进程标识,在start()调用之前可以对其进行修改
exitcode:进程的退出状态码,运行中为None,正常退出为0
name:进程名
pid:进程
join(timeout=)的作用就是阻塞当前的主进程,等子进程结束后才开始主进程。要是子进程也阻塞了,那么这个程序就走不动了,所以有timeout选项,子进程没在timeout时间内完成后,主进程就不等了。
例子,当没有join时,主进程并不会等待子进程
from multiprocessing import Process
import os
import time
def f1(value):
time.sleep(value)
print "into process pid: %s" % os.getpid()
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
p1 = Process(target=f1, args=(2,))
p1.start()
print "main process end"
结果:
main process start: 13196
main process end
into process pid: 4064
有了join,主进程会等待子进程
from multiprocessing import Process
import os
import time
def f1(value):
time.sleep(value)
print "into process pid: %s" % os.getpid()
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
p1 = Process(target=f1, args=(2,))
p1.start()
p1.join()
print "main process end"
结果:
main process start: 6432
into process pid: 13336
main process end
daemon属性默认为False,当设置为True时,主进程将不会等待子进程。
daemon让主进程不等子进程,而join()让主进程阻塞,当两个同时出现时,就算daemon为True也会被join阻塞了,最后还是等待子进程结束。
from multiprocessing import Process
import os
import time
def f1(value):
time.sleep(value)
print "into process pid: %s" % os.getpid()
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
p1 = Process(target=f1, args=(2,))
p1.daemon = True
p1.start()
p1.join()
print "main process end"
结果:
main process start: 13196
into process pid: 11152
main process end
注意,daemon的设置要在start()之前。
2、Pool
当我们要起很多个同样的进程时,Pool就派上用场了,进程池可以生成多个进程。
类的构造方式:
def __init__(self, processes=None, initializer=None, initargs=(),
maxtasksperchild=None):
# processes:进程的数量;若processes是None,默认是cpu的数量
# initializer:若initializer是None,则每一个工作进程在开始的时候就会调initializer(*initargs)
# maxtasksperchild:工作进程退出前可以完成的任务数,完成后用一个新的工作进程来替代原进程,让闲置的# 资源释放,maxtasksperchild默认是None,此意味只要Pool存在工作进程就一直存活
实例方法:
apply_async(func, args=(), kwds={}, callback=None):非阻塞进程,可以同时执行很多个进程,数量跟cpu数有关
apply(func, args=(), kwds={}):阻塞,不管进程多少,只能一个一个执行,等同于单进程
close():关闭pool,不能再添加新的任务
terminate():结束运行的进程,不再处理未完成的任务
join():和Process介绍的作用一样, 但要在close或terminate之后使用
map(func, iterable, chunksize=None):同map
from multiprocessing import Pool
import os
import time
def f1(value):
time.sleep(value)
print "into process pid: %s" % os.getpid()
print time.ctime()
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
p1 = Pool(processes=5)
# for i in range(10):
# p1.apply_async(f1,(i,))
p1.map(f1,range(10))
p1.close()
p1.join()
print "main process end"
map()的效果相当于map和apply_async结合。
3、Value Array
Value、Array是通过共享内存的方式共享数据的。
类的构造方式:
def Value(typecode_or_type, *args, lock):
参数解释:
# typecode_or_type:定义共享内存中分配的对象类型,ctype类型或者ctype类型的code。常用ctype的
# code,整数用i, 字符用c,浮点数用d
# args:传递给ctype的构造参数
# lock:默认值是True。创建一个新的lock来控制对value的访问
def Array(typecode_or_type, size_or_initializer, lock):
参数解释:
# typecode_or_type:定义共享内存中分配的对象类型,ctype类型或者ctype类型的code。常用ctype的
# code,整数用i, 字符用c,浮点数用d
# size_or_initializer:如果它是一个整数,那么它确定数组的长度,并且数组将被初始化为零。否则,
# size_or_initializer是用于初始化数组的序列,其长度决定数组的长度
# lock:默认值是True。创建一个新的lock来控制对value的访问
from multiprocessing import Value, Array, Process
import os
import time
def f1(v, a):
print "into f1"
v.value += 10.0
for i in range(len(a)):
a[i] += 10
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
v = Value('d', 10.0)
a = Array('i', range(10))
p = Process(target=f1, args=(v, a))
p.start()
p.join()
print v.value, a[:]
print "main process end"
输出:
main process start: 1436
into f1
20.0 [10, 11, 12, 13, 14, 15, 16, 17, 18, 19]
main process end
4、Manager
Manager是通过共享进程的方式共享数据。Manager共享的数据类型较多,除了以上说的Array和Value,还支持Python所有数据类型以及共享实例。
from multiprocessing import Manager, Process
import os
import time
def f1(v, a, l, d):
print "into f1"
v.value += 10.0
for i in range(len(a)):
a[i] += 10
l.append(v.value)
d["share"] = a[:]
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
v = Manager().Value('d', 10.0)
a = Manager().Array('i', range(10))
l = Manager().list()
d = Manager().dict()
ps = [Process(target=f1, args=(v, a, l, d)) for i in range(3)]
for p in ps:
p.start()
for p in ps:
p.join()
print v.value, a[:], l, d
print "main process end"
输出:
main process start: 14776
40.0 array('i', [30, 31, 32, 33, 34, 35, 36, 37, 38, 39]) [20.0, 30.0, 40.0] {'share': array('i', [30, 31, 32, 33, 34, 35, 36, 37, 38, 39])}
main process end
5、Queue
Queue实现进程间通信,它是进程安全的,用队列先进先出的顺序,有序地实现数据在进程间的传递。
构造方式:
def Queue(maxsize=0):
#maxsize:队列的长度
实例方法:
put(obj, block=True, timeout=None):将对象obj放入队列。如果block为True,该方法会阻塞timeout值的时间,如果这段时间内,队列都没有空出空间,则抛出Full异常(队列已满异常);如果block为False,队列满了就抛出Full异常。
get(block=True, timeout=None):获取队列里的对象。如果block为True,该方法会阻塞timeout值的时间,如果这段时间内,队列都是空的,则抛出队列enpty异常;如果block为False,队列是空的就抛出异常。
qsize():当前队列中有多少对象。
empty():队列为空,则返回True。
full():队列已满,则返回True。
get_nowait():不等待获取队列中的对象。相当于get(False)。队列为空则抛出Empty异常
put_nowait(obj):不等待将对象放入队列。相当于put(obj,False)。队列满则抛出异常。
close():关闭队列。
用Queue实现生产者消费者模型。
from multiprocessing import Process, Queue
import os
import time
def write(q):
count = 1
while True:
q.put("something")
print "put someting: %s" % count
count += 1
time.sleep(1)
def read(q):
while True:
print q.get()
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
q = Queue(maxsize=5)
p1 = Process(target=write, args=(q,))
p2 = Process(target=read, args=(q,))
p1.start()
p2.start()
p1.join()
p2.join()
print "main process end"
6、Pipe
两个进程之间的数据通信。
构造方式:
def Pipe(duplex=True):
参数解释:
duplex:默认为True。若为True,则该管道是全双工的,两端可同时收同时发;若为False,则第一个管道对象只负责收消息,第二个管道对象只负责发消息。
实例方法:
send(obj):发送对象到另一端。
recv():接收另一端调用send()发过来的对象,如果没有对象可以接收,该方法会一致阻塞。如果另一端已经关闭,则会抛出IOError。
close():关闭管道连接。当连接被垃圾回收的时候会调用这个方法。
fileno():Return the file descriptor or handle used by the connection。
poll(timeout=None):如果连接中的数据是可读的,返回True。没有设定timeout将会立即返回结果,time设置为None则会一直等下去直到数据到达。
send_bytes(buffer, fooset=-1, size=-1):通过连接发送字节数据。如果offset给出,则从缓冲区的相应位置读取; 如果size给出, 则从缓冲读取相应字节的数据(数据超过32M可能抛出异常)。
recv_bytes(maxlength=-1):接收send_bytes()方法发送的一条字节消息。在接收前会一直阻塞;如果没有收到任何信息并且另一端关闭的话将会抛出异常;如果maxlength参数给出并且接收的数据长度超出此参数,将抛出异常,并且该连接将不再可读数据。
recv_bytes_into(buffer, offset=-1):接收一条完整的字节消息,并把它保存在buffer对象中,该对象支持可写入的缓冲区接口(即bytearray对象或类似的对象)。offset指定缓冲区中放置消息处的字节位移。返回值是收到的字节数。如果消息长度大于可用的缓冲区空间,将引发BufferTooShort异常。
import os
from multiprocessing import Process, Pipe
def f1(pipe):
pipe.send("p1 first send")
recv = pipe.recv()
print "p1 first receive: %s" % recv
pipe.send("p1 second send")
recv = pipe.recv()
print "p1 second receive: %s" % recv
def f2(pipe):
pipe.send("p2 first send")
recv = pipe.recv()
print "p2 first receive: %s" % recv
pipe.send("p2 second send")
recv = pipe.recv()
print "p2 second receive: %s" % recv
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
p1, p2 =Pipe()
p1.fileno()
pro1 = Process(target=f1, args=(p1,))
pro2 = Process(target=f2, args=(p2,))
pro1.start()
pro2.start()
pro1.join()
pro2.join()
print "main process end"
输出结果:
main process start: 12584
p2 first receive: p1 first send
p1 first receive: p2 first send
p1 second receive: p2 second send
p2 second receive: p1 second send
main process end
7、Lock
进程加锁,进程间对同一个数据进行操作时,为保护数据安全,同一时间只允许一个进程对数据进行操作。其执行过程类似串行,效率上慢了,安全性高了。
import os
import time
from multiprocessing import Process, Value, Lock
def f1(v, lock):
lock.acquire()
print "into f1, the value is : %s" % v.value
v.value += 1
print "increase 1 to value: %s" % v.value
v.value -= 1
print "decrease 1 to value: %s" % v.value
time.sleep(2)
print "left f1, the value is :%s" % v.value
lock.release()
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
lock = Lock()
v = Value("i", 10)
p_list = [Process(target=f1, args=(v, lock)) for i in range(100)]
for p in p_list:
p.start()
for p in p_list:
p.join()
print "main process end"
上述代码正常情况下,进入和离开打印的值都是10,如果不加锁,进入和离开的值就不确定了。
8、Semaphore
信号量相当于Lock()的高级版,可以控制锁的数量。信号量Semaphore是一个计数器,控制对公共资源或者临界区域的访问量,信号量可以指定同时访问资源或者进入临界区域的进程数。每次有一个进程获得信号量时,计数器-1,若计数器为0时,其他进程就停止访问信号量,一直阻塞直到其他进程释放信号量。
from multiprocessing import Process,Semaphore
import time
import random
def func(i,sem):
sem.acquire()
print('第%s个人进入小黑屋,拿了钥匙锁上门' % i)
time.sleep(random.randint(3,5))
print('第%s个人出去小黑屋,还了钥匙打开门' % i)
sem.release()
if __name__ == '__main__' :
print "main process start: %s" % os.getpid()
sem =Semaphore(5)
for i in range(20) :
p = Process(target=func,args=(i,sem))
p.start()
print "main process end"
9、Event
当某一个进程的处理逻辑需要依赖于另一个进程的状态时,这时候需要用事件来同步。
事件的处理机制:全局定义一个Flag,如果Flag为False,当程序执行wait()方法时,就会阻塞。如果Flag为True,那么wait()就不会再阻塞。
实例方法:
is_set():判断Flag的状态。如果Flag为True,那么返回True,否则返回False。
set():将Flag设置为True。
clear():将Flag设置为False。
wait(timeout=None):等待事件timeout值的时间,如果超时则不等待继续往下执行。
import os
import time
from multiprocessing import Process, Event
def tra(e):
while 1:
if e.is_set():
time.sleep(5)
print 'red light is on!'
e.clear()
else:
time.sleep(5)
print 'green light is on!'
e.set()
def Car(i, e):
e.wait()
print '%s th car has passed!' % i
if __name__ == "__main__":
print "main process start: %s" % os.getpid()
e = Event()
triff_light = Process(target=tra, args=(e,))
triff_light.start()
for i in range(100):
car = Process(target=Car, args=(i + 1, e,))
car.start()
time.sleep(2)
print "main process end"