0x02 进程池相关

进程池

进程池创建时, 会克隆出一个Python解释器, 在Linux上还会克隆原解释器中所有的程序状态, 在Windows中不会包含运行状态.

另外, 如果将多线程和多进程结合在一起使用, 需要在创建任何线程之前, 优先创建并加载进程池.

有两种方法创建进程池.

multiprocessing.Pool

进程池的使用方法如下:

import time
import multiprocessing

def func(msg):
    print("msg:", msg)
    time.sleep(3)
    print("end")

pool = multiprocessing.Pool(processes=3)
for i in range(4):
    msg = "hello %d" %(i)
    pool.apply_async(func, (msg, ))

pool.close()
pool.join()
print("Sub-process(es) done.")

注意:

  • 使用apply_asyncapply方法向子进程中添加任务, 使用这两种方法添加任务之后, 会立即在子进程中启动运行. 两者的区别在于前者是非阻塞的, 后者是阻塞的, 即使用apply方法添加后, 需要等待这个任务执行完毕, 主进程才能继续执行.

    上述代码的执行结果为:

    msg: hello 0
    msg: hello 1
    msg: hello 2
    end
    msg: hello 3
    end
    end
    end
    Sub-process(es) done.

    如果上述代码的apply_async方法改为apply方法, 执行的结果就是:

    msg: hello 0
    end
    msg: hello 1
    end
    msg: hello 2
    end
    msg: hello 3
    end
    Sub-process(es) done.
  • close方法的作用是关闭进程池, 进程池不再接受新的任务, join方法的作用是阻塞父进程. 因此在调用阻塞方法时, 需要先调用进程池的close方法, 然后再调用join方法进行阻塞.

  • terminate()方法才是真正的关闭进程池的方法, 结束所有工作子进程, 不再处理未完成的任务.

  • 为了拿到任务的执行结果, 可以将apply_async方法的返回结果进行收集:

    ```python

    coding: utf-8

    import time import multiprocessing

def func(msg): print("msg:", msg) time.sleep(3) print("end")

if name == "main": pool = multiprocessing.Pool(processes=3) results = [] for i in range(4): msg = "hello %d" % (i,) results.append(pool.apply_async(func, (msg,)))

  print(type(results[0]))
  pool.close()
  pool.join()
  输出为:

msg: hello 0 msg: hello 1 msg: hello 2 end msg: hello 3 end end end

```

  • apply_async返回的对象收集起来, 如上面代码使用列表来收集

  • apply_async返回的对象是multiprocessing.pool.ApplyResult对象, 使用该对象的get()方法得到任务函数返回的结果, 如果进程还没有执行完, get()方法将会产生阻塞.

concurrent.futures.ProcessPoolExecutor

如同ThreadPoolExecutor的使用方法, 用submit或者map方法提交任务.

两者的区别

ProcessPoolExecutor from concurrent.futures way slower than multiprocessing.Pool中有一个关于两者之间速度的问题. 在处理较多任务的时候, ProcessPoolExecutor方法会慢很多, 这主要是因为:

ProcessPoolExecutor对于每一个提交的任务, 都会创建一个Future对象, 里面会装在很多内容, 如最后运行的结果. 但multiprocessing.Pool就不会有这一步, 如果使用它的map方法进行提交, Pool对象会批量地提交任务, 然后直接就把结果返回, 不会产生额外的开销.

因此:

  • 大批量任务的应用场景下, 使用multiprocessing.Pool进行多进程是更好的选择.

  • 当任务执行的时间较长时, 使用ProcessPoolExecutor带来的Futures更好.

  • 如果需要在处理完毕后执行callback, 就只能使用ProcessPoolExecutor了, 因为Pool没有callback的方法, 只能手动实现.

最后更新于