首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Python并发编程应该使用哪个标准库?

Python并发编程应该使用哪个标准库?

作者头像
somenzz
发布2020-11-25 10:30:53
1.8K0
发布2020-11-25 10:30:53
举报
文章被收录于专栏:Python七号Python七号

阅读本文大概需要 5 分钟。

并发编程是刚需,尤其是在多 I/O 操作时,多线程,协程,多进程三路英雄各显神通。多线程,协程属于并发操作,多进程属于并行操作,那么你是否清楚了什么是并发,什么是并行?

并发与并行的区别

借用知乎用户的回答:

你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。 并发的关键是你有处理多个任务的能力,不一定要同时。 并行的关键是你有同时处理多个任务的能力。

多线程:在 Python 里,由于有全局锁 (GIL) 的存在,并发就是多个线程轮流使用 CPU,同一时刻只一个线程在工作,操作系统会在合适的时间进行切换,由于线程的切换速度非常快,给人的感觉是多个任务都在运行。在 I/O 密集型任务场景中,线程切换后,I/O 操作仍然在进行,线程 1 在进行 I/O 操作时,线程 2 可以获得 CPU 资源进行计算,虽然增加了切换成本,却提高了效率。

协程:协程是轻量级线程,是单线程,却可以执行并发任务,原因是协程把切换的权利交给程序员,与程序员决定在哪些环节进行切换。协程可以处理上万的并发,多线程即不可以,因为切换成本太大,会耗尽计算机资源,可以搜索下 C10K 问题。

多进程:并行,真正的同一时刻多个任务同时进行。如果想使用多核,就选多进程。

Python 协程标准库只有一个,即 asyncio,而支持多线程,多进程的标准库却有两个:Concurrent.futures 和 Multiprocessing。本文分享一下这两者的使用区别。先看下基本用法。

Multiprocessing 的基本用法

Multiprocessing 即有线程池,也是进程池,简单的使用方法如下:

线程池:

from multiprocessing.dummy import Pool as ThreadPool
with ThreadPool(processes=100) as executor:
    executor.map(func, iterable)

进程池:

from multiprocessing import Pool as ProcessPool
with ProcessPool(processes=10) as executor:
    executor.map(func, iterable)

Concurrent.futures 的基本用法

线程池:

from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor(max_workers=5) as executor:
     executor.map(function, iterable)

进程池:

from concurrent.futures import ProcessPoolExecutor
with ProcessPoolExecutor(max_workers=5) as executor:
     executor.map(function, iterable)

有没有觉得他们的使用方法简直一模一样,那么官方为何要提供这样两个标准库呢?

二者区别

其实,本质区别并不大,有的也只是调用方式略有差异。先有的 multiprocessing,后有的 concurrent.futures,后者的出现就是为了降低编写代码的难度,后者的学习成本较低。

在速度上,并无谁快谁慢之说。获得多少加速(如果有)取决于硬件,操作系统的详细信息,尤其取决于特定任务需要多少进程间通信。在后台,所有进程都依赖于相同的 OS 原语,使用这些原语的高级 API 并不是j影响速度的主要因素。接下来分享下二者的详细用法。

关于 concurrent.futures

官方说 concurrent.futures 模块是更高级别的接口,主要是因为它让程序员并发和并行的代码更简单了。该模块提供以下对象和函数:

  • 期程对象:concurrent.futures.Future
  • 模块函数:concurrent.futures.wait
  • 执行器对象:concurrent.futures.{Executor,ThreadPoolExecutor,ProcessPoolExecutor}

比如,Futures 中的 Executor 类,当我们执行 executor.submit(func) 时,它便会安排里面的 func() 函数执行,并返回创建好的 future 实例,以便你之后查询调用。

这里再介绍一些常用的函数。Futures 中的方法 done(),表示相对应的操作是否完成——True 表示完成,False 表示没有完成。不过,要注意,done() 是 non-blocking 的,会立即返回结果。相对应的 add_done_callback(fn),则表示 Futures 完成后,相对应的参数函数 fn,会被通知并执行调用。

Futures 中还有一个重要的函数 result(),它表示当 future 完成后,返回其对应的结果或异常。而 as_completed(fs),则是针对给定的 future 迭代器 fs,在其完成后,返回完成后的迭代器。

官方给的 ThreadPoolExecutor 例子:

import concurrent.futures
import urllib.request

URLS = ['http://www.foxnews.com/',
        'http://www.cnn.com/',
        'http://europe.wsj.com/',
        'http://www.bbc.co.uk/',
        'http://some-made-up-domain.com/']

# Retrieve a single page and report the URL and contents
def load_url(url, timeout):
    with urllib.request.urlopen(url, timeout=timeout) as conn:
        return conn.read()

# We can use a with statement to ensure threads are cleaned up promptly
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
    # Start the load operations and mark each future with its URL
    future_to_url = {executor.submit(load_url, url, 60): url for url in URLS}
    for future in concurrent.futures.as_completed(future_to_url):
        url = future_to_url[future]
        try:
            data = future.result()
        except Exception as exc:
            print('%r generated an exception: %s' % (url, exc))
        else:
            print('%r page is %d bytes' % (url, len(data)))

请注意:

ProcessPoolExecutor 是 Executor 的子类,它使用进程池来实现异步执行调,使用 multiprocessing 回避 Global Interpreter Lock 但也意味着,作为进程的函数只可以处理和返回可序列化的对象, __main__ 模块必须可以被子进程导入,这意味着 ProcessPoolExecutor 不可以工作在交互式解释器中。

关于 multiprocessing

multiprocessing 是一个用于产生进程的包,具有与 threading 模块相似 API。multiprocessing 包同时提供本地和远程并发,使用子进程代替线程,有效避免 Global Interpreter Lock 带来的影响。因此,multiprocessing 模块允许程序员充分利用机器上的多核。可运行于 Unix 和 Windows 。

multiprocessing 模块还引入了在 threading 模块中没有的 API。一个主要的例子就是 Pool 对象,它提供了一种快捷的方法,赋予函数并行化处理一系列输入值的能力,可以将输入数据分配给不同进程处理(数据并行)。下面的例子演示了在模块中定义此类函数的常见做法,以便子进程可以成功导入该模块。这个数据并行的基本例子使用了 Pool ,

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]))

结论

因此,简单的并发应用,请使用 concurrent.futures,复杂些的,要自己动手实现的,请使用 multiprocessing 吧。初学者直接学习 concurrent.futures。

参考文档:

https://docs.python.org/zh-cn/3/library/concurrent.futures.html

https://docs.python.org/zh-cn/3/library/multiprocessing.html

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-11-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Python七号 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 并发与并行的区别
  • Multiprocessing 的基本用法
  • Concurrent.futures 的基本用法
  • 二者区别
    • 关于 concurrent.futures
      • 关于 multiprocessing
      • 结论
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档