前往小程序,Get更优阅读体验!
立即前往
发布
社区首页 >专栏 >协程和异步I/O

协程和异步I/O

作者头像
用户8442333
修改2021-05-21 10:13:12
修改2021-05-21 10:13:12
81800
代码可运行
举报
文章被收录于专栏:python知识python知识
运行总次数:0
代码可运行
协程的概念

协程(coroutine)通常又称之为微线程或纤程,它是相互协作的一组子程序(函数)。所谓相互协作指的是在执行函数A时,可以随时中断去执行函数B,然后又中断继续执行函数A。注意,这一过程并不是函数调用(因为没有调用语句),整个过程看似像多线程,然而协程只有一个线程执行。协程通过yield关键字和 send()操作来转移执行权,协程之间不是调用者与被调用者的关系。

协程的优势在于以下两点:

  1. 执行效率极高,因为子程序(函数)切换不是线程切换,由程序自身控制,没有切换线程的开销。
  2. 不需要多线程的锁机制,因为只有一个线程,也不存在竞争资源的问题,当然也就不需要对资源加锁保护,因此执行效率高很多。

说明:协程适合处理的是I/O密集型任务,处理CPU密集型任务并不是它的长处,如果要提升CPU的利用率可以考虑“多进程+协程”的模式。

历史回顾
  1. Python 2.2:第一次提出了生成器(最初称之为迭代器)的概念(PEP 255)。
  2. Python 2.5:引入了将对象发送回暂停了的生成器这一特性即生成器的send()方法(PEP 342)。
  3. Python 3.3:添加了yield from特性,允许从迭代器中返回任何值(注意生成器本身也是迭代器),这样我们就可以串联生成器并且重构出更好的生成器。
  4. Python 3.4:引入asyncio.coroutine装饰器用来标记作为协程的函数,协程函数和asyncio及其事件循环一起使用,来实现异步I/O操作。
  5. Python 3.5:引入了asyncawait,可以使用async def来定义一个协程函数,这个函数中不能包含任何形式的yield语句,但是可以使用returnawait从协程中返回值。
示例代码
  1. 生成器 - 数据的生产者。 from time import sleep # 倒计数生成器 def countdown(n): while n > 0: yield n n -= 1 def main(): for num in countdown(5): print(f'Countdown: {num}') sleep(1) print('Countdown Over!') if __name__ == '__main__': main() 生成器还可以叠加来组成生成器管道,代码如下所示。 # Fibonacci数生成器 def fib(): a, b = 0, 1 while True: a, b = b, a + b yield a # 偶数生成器 def even(gen): for val in gen: if val % 2 == 0: yield val def main(): gen = even(fib()) for _ in range(10): print(next(gen)) if __name__ == '__main__': main()
  2. 协程 - 数据的消费者。 from time import sleep # 生成器 - 数据生产者 def countdown_gen(n, consumer): consumer.send(None) while n > 0: consumer.send(n) n -= 1 consumer.send(None) # 协程 - 数据消费者 def countdown_con(): while True: n = yield if n: print(f'Countdown {n}') sleep(1) else: print('Countdown Over!') def main(): countdown_gen(5, countdown_con()) if __name__ == '__main__': main() 说明:上面代码中countdown_gen函数中的第1行consumer.send(None)是为了激活生成器,通俗的说就是让生成器执行到有yield关键字的地方挂起,当然也可以通过next(consumer)来达到同样的效果。如果不愿意每次都用这样的代码来“预激”生成器,可以写一个包装器来完成该操作,代码如下所示。 from functools import wraps def coroutine(fn): @wraps(fn) def wrapper(*args, **kwargs): gen = fn(*args, **kwargs) next(gen) return gen return wrapper 这样就可以使用@coroutine装饰器对协程进行预激操作,不需要再写重复代码来激活协程。
  3. 异步I/O - 非阻塞式I/O操作。 import asyncio @asyncio.coroutine def countdown(name, n): while n > 0: print(f'Countdown[{name}]: {n}') yield from asyncio.sleep(1) n -= 1 def main(): loop = asyncio.get_event_loop() tasks = [ countdown("A", 10), countdown("B", 5), ] loop.run_until_complete(asyncio.wait(tasks)) loop.close() if __name__ == '__main__': main()
  4. asyncawait
代码语言:javascript
代码运行次数:0
复制
import asyncio
import aiohttp


async def download(url):
    print('Fetch:', url)
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as resp:
            print(url, '--->', resp.status)
            print(url, '--->', resp.cookies)
            print('\n\n', await resp.text())


def main():
    loop = asyncio.get_event_loop()
    urls = [
        'https://www.baidu.com',
        'http://www.sohu.com/',
        'http://www.sina.com.cn/',
        'https://www.taobao.com/',
        'https://www.jd.com/'
    ]
    tasks = [download(url) for url in urls]
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()


if __name__ == '__main__':
    main()

本文系转载,前往查看

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

本文系转载前往查看

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 协程的概念
  • 历史回顾
  • 示例代码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档