Python多进程
多进程
由于GIL(全局解释锁)的问题,多线程并不能充分利用多核处理器,如果是一个CPU计算型的任务,应该使用多进程模块 multiprocessing 。它的工作方式与线程库完全不同,但是两种库的语法和接口却非常相似。multiprocessing给每个进程赋予单独的Python解释器,这样就规避了全局解释锁所带来的问题。
import time
import multiprocessing
from multiprocessing.pool import Pool
from multiprocessing.dummy import Pool as ThreadPool
def profile(func):
def wrapper(*args, **kwargs):
import time
start = time.time()
func(*args, **kwargs)
end = time.time()
print('COST: {}'.format(end - start))
return wrapper
def fib(n):
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)
@profile
def nomultiprocess():
fib(35)
fib(35)
@profile
def hasmultiprocess():
jobs = []
for i in range(2):
p = multiprocessing.Process(target=fib, args=(35,))
p.start()
jobs.append(p)
for job in jobs:
job.join()
if __name__ == '__main__':
nomultiprocess()
hasmultiprocess()
进程池
from multiprocessing import Pool
pool = Pool(2) #默认启动的进程/线程数都为CPU数,如果python获取不到CPU数则默认为1
result=pool.map(fib, [35] * 2)
或者:
result=[]
for n in [35,35]:
result.append(pool.apply(fib, n) )
线程池
如果分不清任务是CPU密集型还是IO密集型,就用如下2个方法分别试:
from multiprocessing import Pool
from multiprocessing.dummy import Pool
哪个速度快就用那个。从此以后尽量在写兼容的方式,这样在多线程/多进程之间切换非常方便。
如果一个任务拿不准是CPU密集还是I/O密集型,且没有其它不能选择多进程方式的因素,现在都统一直接用多进程。
进程间通信
Queue
多线程有Queue模块实现队列,多进程模块也包含了Queue类,它是线程和进程安全的。
进程的Queue类并不支持task_done和join方法,需要使用特别的JoinableQueue。
import random
import time
from multiprocessing import Process, Queue, JoinableQueue
def double(n):
return n * 2
def producer(in_queue):
while True:
wt = random.random()
in_queue.put((double, wt))
print('produce: ', wt)
time.sleep(wt)
if wt > 0.9:
in_queue.put(None)
print('stop producer')
break
def consumer(in_queue, out_queue):
while True:
task = in_queue.get()
if task is None:
break
func, arg = task
result = func(arg)
print('consumer result: ', result)
out_queue.put(result)
in_queue.task_done()
if __name__ == '__main__':
tasks_queue = JoinableQueue() # 进程类的Queue不支持join与task_done方法
results_queue = Queue()
processs = []
p = Process(target=producer, args=(tasks_queue,))
p.start()
processs.append(p)
p = Process(target=consumer, args=(tasks_queue, results_queue))
p.start()
processs.append(p)
tasks_queue.join()
for p in processs:
p.join()
while results_queue.qsize() > 0:
print(results_queue.get())
生产者已经不会持续的生产任务了,如果随机到的结果大于0.9就会给任务队列tasks_queue put一个None,然后把循环结束掉
消费者如果收到一个值为None的任务,就结束,否则执行从tasks_queue获取的任务,并把结果put进results_queue
生产者和消费者都结束后(有join方法保证),从results_queue挨个获取执行结果并打印出来
Pipe
Pipe返回的是管道2边的对象:「父连接」和「子连接」
def f(conn):
conn.send(['hello'])
conn.close()
parent_conn, child_conn = Pipe()
if __name__ == '__main__':
p = Process(target=f, args=(child_conn,))
p.start()
p.join()
print(parent_conn.recv())
当子连接发送一个带有hello字符串的列表,父连接就会收到,所以 parent_conn.recv()就会打印出来。