python协程
不知道你有没有被问到过有没有使用过的python协程?
协程是什么?
协程是一种用户态轻量级,是实现并发编程的一种方式。说到并发,就能想到了多线程 / 多进程模型,是解决并发问题的经典模型之一。
但是随刻客户端数量达到一定量级,进程上下文切换占用了大量的资源,线程也顶不住如此巨大的压力,对于多线程应用,CPU通过切片的方式来切换线程间的执行,
线程切换时需要耗时(保存状态,下次继续)。协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。线程是抢占式的调度,而协程是协同式的调度,也就是说,协程需要自己做调度。
协程有什么用?
在别的语言中协程意义不大,多线程可解决问题,但是python因为他有GIL(Global Interpreter Lock 全局解释器锁 )在同一时间只有一个线程在工作,如果一个线程里面I/O操作特别多,协程就比较适用;
在python中多线程的执行情况如下图:
怎么实现协程?
python2.7中用生成器实现协程
在python3.7以后使用asyncio 和 async / await 的方法实现协程,实现过程就变得很简单
具体说明:简单的代码示例
import asyncio
import time
async def sub_function(str):
print(' {}'.format(str))
sleep_time = int(str.split('_')[-1])
await asyncio.sleep(sleep_time)
print('OK {}'.format(str))
async def main(strs):
for str in strs:
await sub_function(str)
# asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.run
t0 = time.time()
asyncio.run(main(['str_1', 'str_2', 'str_3', 'str_4']))
t1 = time.time()
print("Total time running: %s seconds" %(str(t1 - t0)))
输出结果:一共是 10s 和我们顺序分析 分别等待 1 2 3 4 秒 好像没有什么提升作用,继续往下看
执行协程的方法有三种:
1. 入上例所示,用await 来调用实现,但是await 执行的效果,和 Python 正常执行是一样的,也就是说程序会阻塞在这里,进入被调用的协程函数,执行完毕返回后再继续,而这也是 await 的字面意思。
await 是同步调用,相当于我们用异步接口写了个同步代码,所以运行时间没有得到提升。
2. 上栗的异步想过没有体现出来,接下来我们使用另一个概念 task
async def sub_function(str): print(' {}'.format(str)) sleep_time = int(str.split('_')[-1]) await asyncio.sleep(sleep_time) print('OK {}'.format(str)) async def main(strs):
# tasks = [asyncio.create_task(sub_function(str)) for str in strs] for task in tasks: await task # asyncio.run(main()) 作为主程序的入口函数,在程序运行周期内,只调用一次 asyncio.run t0 = time.time() asyncio.run(main(['str_1', 'str_2', 'str_3', 'str_4'])) t1 = time.time() print("Total time running: %s seconds" %(str(t1 - t0)))
们有了协程对象后,便可以通过 asyncio.create_task 来创建任务。任务创建后很快就会被调度执行,
这样,我们的代码也不会阻塞在任务这里。用for task in tasks: await task 即可。这次,你就看到效果了吧,结果显示,运行总时长等于运行时间最长一句。
运行结果:
3. 上面的task也可以用下面的方式写:
async def main(strs): tasks = [asyncio.create_task(sub_function(str)) for str in strs] await asyncio.gather(*tasks)
唯一要注意的是,*tasks 解包列表,将列表变成了函数的参数;与之对应的是, ** dict 将字典变成了函数的参数。
在实际中,我们会遇到接口超时,我们就需要取消的情况,这种情况该怎么处理呢?再进一步,如果某些协程运行时出现错误,又该怎么处理呢?
import time import asyncio async def worker_1(): await asyncio.sleep(1) return 1 async def worker_2(): await asyncio.sleep(2) return 2 / 0 async def worker_3(): await asyncio.sleep(3) return 3 async def main(): task_1 = asyncio.create_task(worker_1()) task_2 = asyncio.create_task(worker_2()) task_3 = asyncio.create_task(worker_3()) await asyncio.sleep(2) task_3.cancel() res = await asyncio.gather(task_1, task_2, task_3, return_exceptions=True) print(res) t0 = time.time() asyncio.run(main()) t1 = time.time() print("Total time running: %s seconds" %(str(t1 - t0)))
要注意return_exceptions=True这行代码。这个参数默认值为False, 如果不设置这个参数,错误就会完整地 throw 到我们这个执行层,从而需要 try except 来捕捉,
这也就意味着其他还没被执行的任务会被全部取消掉。为了避免这个局面,我们将 return_exceptions 设置为 True 即可。
线程能实现的,协程都能做到.