乐趣区

关于python:协程是旅行吗

​python 始终在进行并发编程的优化,比拟熟知的是应用 thread 模块多线程和 multiprocessing 多过程,起初缓缓引入基于 yield 关键字的协程。而近几个版本,python 对于协程的写法进行了大幅的优化,很多之前的协程写法不被官网举荐了。

协程倒退历程

Python 中的协程大略经验了如下三个阶段:

  1. 最后的生成器变形 yield/send
  2. yield from
  3. 在 Python3.5 版本中引入 async/await 关键字

本文就间接从 Python3.5 版本开始讲起,如果大家有趣味能够讲讲在没有呈现 async/await 关键字之前,Python 是如何实现协程的

同步和异步

同步是指代码调用 IO 操作时, 必须期待 IO 操作实现才返回的调用形式, 多个工作之间执行的时候要求有先后顺序,必须一个先执行实现之后,另一个能力继续执行,只有一个主线

异步是指代码调用 IO 操作时, 不用等 IO 操作实现就返回的调用形式, 多个工作之间执行没有先后顺序,能够同时运行,执行的先后顺序不会有什么影响,存在的多条运行主线

并行和并发

我拿单核 CPU 来说,单核意味着同一时刻只做一件事件, 那么电脑上同时运行的软件是如何做到的呢?

QQ 到 CPU 中执行很短的工夫,而后微信在到 CPU 中执行很短的工夫,最终大家看起来就像是一起来运行,CPU 的这种轮转策略就叫做工夫片轮转

并行:cpu 大于以后执行的工作

并发:cpu 小于以后执行的工作

协程,线程和过程的比照

  1. 如果有一条生产线,在这条生产线上多招些工人,一起来做剪子,这样效率是成倍増长,即单过程多线程形式
  2. 老板发现这条生产线上的工人不是越多越好,因为一条生产线的资源以及资料毕竟无限,所以老板又花了些财力物力购买了另外一条生产线,而后再招些工人这样效率又再一步进步了,即多过程多线程形式

3. 老板发现,当初曾经有了很多条生产线,并且每条生产线上曾经有很多工人了(即程序是多过程的,每个过程中又有多个线程),为了再次提高效率,老板想了个损招,规定:如果某个员工在下班时长期没事或者再期待某些条件(比方期待另一个工人生产完谋道工序 之后他能力再次工作),那么这个员工就利用这个工夫去做其它的事件,那么也就是说:如果一个线程期待某些条件,能够充分利用这个工夫去做其它事件,其实这就是:协程形式

协程的根底应用

这是 python 3.7 外面的根底协程用法,当初这种用法曾经根本稳固,不太倡议应用之前的语法了。

import asyncio
import time
async def visit_url(url, response_time):
    """拜访 url"""
    await asyncio.sleep(response_time)
    return f"拜访 {url}, 已失去返回后果"
start_time = time.perf_counter()
task = visit_url('http://wangzhen.com', 2)
asyncio.run(task)
print(f"耗费工夫:{time.perf_counter() - start_time}")
  • 在一般的函数后面加 async 关键字;
  • await 示意在这个中央期待子函数执行实现,再往下执行。(在并发操作中,把程序控制权教给主程序,让他调配其余协程执行。)await 只能在带有 async 关键字的函数中运行。
  • asynico.run() 运行程序
  • 这个程序耗费工夫 2s 左右。

减少协程

再增加一个工作

task2 = visit_url('http://another.com', 3)
asynicio.run(task2)

这 2 个程序一共耗费 5s 左右的工夫。并没有施展并发编程的劣势

import asyncio
import time
async def visit_url(url, response_time):
    """拜访 url"""
    await asyncio.sleep(response_time)
    return f"拜访 {url}, 已失去返回后果"
async def run_task():
    """收集子工作"""
    task = visit_url('http://wangzhen.com', 2)
    task_2 = visit_url('http://another', 3)
    await asyncio.run(task)
    await asyncio.run(task_2)
asyncio.run(run_task())
print(f"耗费工夫:{time.perf_counter() - start_time}")

如果是并发编程,这个程序只须要耗费 3s, 也就是 task2 的等待时间。要想应用并发编程模式,须要把下面的代码改一下。asyncio.gather 会创立 2 个子工作,当呈现 await 的时候,程序会在这 2 个子工作之间进行调度。

async def run_task():
    """收集子工作"""
    task = visit_url('http://wangzhen.com', 2)
    task_2 = visit_url('http://another', 3)
    await asynicio.gather(task1, task2)

create_task

创立子工作除了能够用 gather 办法之外,还能够应用 asyncio.create_task 进行创立。

async def run_task():
    coro = visit_url('http://wangzhen.com', 2)
    coro_2 = visit_url('http://another.com', 3)
    task1 = asyncio.create_task(coro)
    task2 = asyncio.create_task(coro_2)
    await task1
    await task2

协程的次要应用场景

协程的次要利用场景是 IO 密集型工作,总结几个常见的应用场景:

  • 网络申请,比方爬虫,大量应用 aiohttp
  • 文件读取,aiofile
  • web 框架,aiohttp,fastapi
  • 数据库查问,asyncpg, databases

退出移动版