说明
Python标准库为我们提供了threading和multiprocessing模块编写相应的多线程/多进程代码。
从Python3.2开始,标准库为我们提供了concurrent.futures模块,concurrent.futures 模块的主要特色是 ThreadPoolExecutor 和
ProcessPoolExecutor 类,这两个类实现的接口能分别在不同的线程或进程中执行可调
用的对象。这两个类在内部维护着一个工作线程或进程池,以及要执行的任务队列。
Python 3.4 以后标准库中asyncio 包,这个包使用事件循环驱动的协程实现并发。这是 Python 中最大也
是最具雄心壮志的库之一。asyncio 大量使用 yield from 表达式,因此与
Python 旧版不兼容。
submit和map方法
submit方法作用是向线程池提交可回调的task,并返回一个回调实例。
example:
import time
from concurrent.futures import ThreadPoolExecutor
# 可回调的task
def pub_task(msg):
time.sleep(3)
return msg
# 创建一个线程池
pool = ThreadPoolExecutor(max_workers=3)
# 往线程池加入2个task
task1 = pool.submit(pub_task, 'a')
task2 = pool.submit(pub_task, 'b')
print(task1.done()) # False
time.sleep(4)
print(task2.done()) # True
print(task1.result())
print(task2.result())
map方法是创建一个迭代器,回调的结果有序放在迭代器中。
问题:
Executor.map 函数易于使用,不过有个特性可能有用,也可能没用,具体情况取决于需
求:这个函数返回结果的顺序与调用开始的顺序一致。如果第一个调用生成结果用时 10
秒,而其他调用只用 1 秒,代码会阻塞 10 秒,获取 map 方法返回的生成器产出的第一个
结果。在此之后,获取后续结果时不会阻塞,因为后续的调用已经结束。如果必须等到获
取所有结果后再处理,这种行为没问题;不过,通常更可取的方式是,不管提交的顺序,
只要有结果就获取。为此,要把 Executor.submit 方法和 futures.as_completed 函
数结合起来使用。
from concurrent.futures import ThreadPoolExecutor
import requests
URLS = ['http://www.csdn.com', 'http://qq.com', 'http://www.leasonlove.cn']
def task(url, timeout=10):
return requests.get(url, timeout=timeout)
pool = ThreadPoolExecutor(max_workers=3)
results = pool.map(task, URLS)
for ret in results:
print('%s, %s' % (ret.url, ret))
future异步编程
Future可以理解为一个在未来完成的操作,这是异步编程的基础。通常情况下,我们执行io操作,访问url时(如下)在等待结果返回之前会产生阻塞,cpu不能做其他事情,而Future的引入帮助我们在等待的这段时间可以完成其他的操作。
from concurrent.futures import ThreadPoolExecutor
from concurrent.futures import as_completed
import requests
URLS = ['http://www.csdn.cn', 'http://qq.com', 'http://www.leasonlove.cn']
def task(url, timeout=1):
return requests.get(url, timeout=timeout)
with ThreadPoolExecutor(max_workers=3) as executor:
future_tasks = [executor.submit(task, url) for url in URLS]
for f in future_tasks:
if f.running():
print('%s is running' % str(f))
for f in as_completed(future_tasks):
try:
ret = f.done()
if ret:
f_ret = f.result()
print('%s, done, result: %s, %s' % (str(f), f_ret.url, f_ret.content))
except Exception as e:
# 第一个url无响应
f.cancel()
print(str(e))
asyncio库协程实现并发
对于gevent 和 asyncio
建议大家放弃Gevent,拥抱asyncio,asyncio是Python3.4以后标准库。而且由于Gevent直接修改标准库里面大部分的阻塞式系统调用,包括socket、ssl、threading和 select等模块,而变为协作式运行。但是我们无法保证你在复杂的生产环境中有哪些地方使用这些标准库会由于打了补丁而出现奇怪的问题。
import asyncio
import time
start = time.time()
async def do(x):
print('Waiting: ', x)
await asyncio.sleep(x)
return 'Finish after {}s'.format(x)
task1 = do(1)
task2 = do(2)
task3 = do(4)
tasks = [
asyncio.ensure_future(task1),
asyncio.ensure_future(task2),
asyncio.ensure_future(task3)
]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
for task in tasks:
print('Task result: ', task.result())
end = time.time()
print('TIME: ', end - start)
协程与线程
如果使用线程做过重要的编程,你就知道写
出程序有多么困难,因为调度程序任何时候都能中断线程。必须记住保留锁,去保护程序
中的重要部分,防止多步操作在执行的过程中中断,防止数据处于无效状态。
而协程默认会做好全方位保护,以防止中断。我们必须显式产出才能让程序的余下部分运
行。对协程来说,无需保留锁,在多个线程之间同步操作,协程自身就会同步,因为在任
意时刻只有一个协程运行。想交出控制权时,可以使用 yield 或 yield from 把控制权
交还调度程序。这就是能够安全地取消协程的原因:按照定义,协程只能在暂停的 yield
处取消,因此可以处理 CancelledError 异常,执行清理操作。
参考资料:fluent Python