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 即可。

 线程能实现的,协程都能做到.

原文地址:https://www.cnblogs.com/qiutian-guniang/p/12822824.html