如果某个任务式计算密集型的,使用线程不见得能加快执行效率,还有可能变得更缓慢。针对于计算密集型的运算,使用线程不但不能加快效率,还有可能因为线程的频繁切换而变得更慢。针对于计算密集型的运算,如果能在一个新的进程并行运行,在如今计算机都有多个核心处理器的情况下,就有机会运行的更快一些。
muliprocessing
前面我们提到了线程的threading模块,那么类似的想要以子进程来执行函数,像threading模块的API接口那样,那么可以使用multiprocessing模块。示例代码如下:
import sys, multiprocessing
def foo(filename, queue):
with open(filename,queue):
text = f.read()
ct=0
for ch in text:
n=ord(ch.upper())+1
if n==67:
ct+=1
queue.put(ct)
if __name__ == '__main__':
queue = multiprocessing.Queue()
#下面式建立一个类似‘进程池’的列表,里面保存了要处理的函数进程,可以看出基本用法与threading类似
ps = [multiprocessing.Process(target=foo, args=(filename, queue)) for filename in sys.argv[1:]]
for p in ps:
#同样需要启动进程
p.start()
for p in ps:
#因为我们最后要在全部进程执行完后获取结果进行处理,所以必须等待全部进程完成
p.join()
count=0
while not queue.empty():
count=queue.get()
print(count)
虽然建议在使用multiprocessing模块时不要共享状态,然而有时进程之间难免需要进行沟通,multiprocessing.Queue类似前面我们使用的queue.Queue,在进程之间实现了安全、必要的锁定机制。
当然我们也可以像线程那样通过锁来控制并行的顺序,如下面的例子:
from multiprocessing import Process
def f(i):
print('hello world', i)
print('hello world',i+1)
if __name__ == '__main__':
for num in range(100):
Process(target=f, args=(num,)).start()
运行几次你会发现,1-100并不是每次连续输出,这就是因为各个进程竞争标准输出的关系,那么我们该怎么办呢?注意i是公共变量,自然要想到加锁了,每次进程输出两个数后才能解锁,修改代码如下:
from multiprocessing import Process,Lock
def f(lock, i):
with lock:
print('hello world', i)
print('hello world',i+1)
if __name__ == '__main__':
lock=Lock()
for num in range(100):
Process(target=f, args=(lock,num,)).start()
然而我在平时编程中最常用的不只上面两项,multiprocessing模块也提供了一些不同于threading模块的API,例如Pool,这可以创建一个进程工作者池,这一般在想要启动大量的子进程时进行使用,下面举个例子, 还是上面处理文件的程序:
import sys, multiprocessing
def foo(filename):
with open(filename) as f:
text = f.read()
ct=0
for ch in text:
n = ord(ch.upper())
if n==67:
ct+=1
return ct
if __name__ == '__main__':
filenames = sys.argv[1:]
#创建一个可以同时跑两个进程的进程池
with multiprocessing.Pool(2) as pool:
results = [pool.apply_async(foo,(filename,)) for filename in filenames]
pool.close()
pool.join()
count = sum(result.get() for result in results)
print(count)
还有常用的pool.map实例, 先来看一下官方文档上的实例:
from multiprocessing import Pool
def f(x):
return x*x
if __name__ == '__main__':
with Pool(5) as p:
print(p.map(f, [1, 2, 3]))
这里简单阐述一下我对pool.map和pool.apply_async的理解,map会阻塞主进程,一直到运行结束才会执行下面语句,而applyasync()的话不会阻塞主进程,两个的运行应该都是异步的,但是map会按迭代器的顺序返回结果。