Python并发和并行(8)——引入进程

如果某个任务式计算密集型的,使用线程不见得能加快执行效率,还有可能变得更缓慢。针对于计算密集型的运算,使用线程不但不能加快效率,还有可能因为线程的频繁切换而变得更慢。针对于计算密集型的运算,如果能在一个新的进程并行运行,在如今计算机都有多个核心处理器的情况下,就有机会运行的更快一些。

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会按迭代器的顺序返回结果。

你可能感兴趣的:(Python并发和并行(8)——引入进程)