python3 协程实战(python3经典编程案例)

简介: 该文章通过多个实战案例介绍了如何在Python3中使用协程来提高I/O密集型应用的性能,利用asyncio库以及async/await语法来编写高效的异步代码。

一. 定义协程

协程是轻量级线程,拥有自己的寄存机上下文和栈。
协程调度切换时,将寄存器上下文和栈保存到其他地方,再切回来时,恢复先前保存的寄存器上下文和栈。

协程的应用场景:I/O密集型任务,和多线程类似,但协程调用时在一个线程内进行的,是单线程,切换的开销小,因此,效率上略高于多线程。

python3.4加入了协程,以生成器对象为基础,python3.5加了async/await,使用协程更加方便。

python中使用协程最方便的库是asyncio,引入该库才能使用async和await关键字

  • async: 定义一个协程;async定义的方法无法直接执行,必须注册到时间循环中才能执行。
  • await: 用于临时挂起一个函数或方法的执行。

根据官方文档,await后面的对象必须是如下类型之一:

  • 一个原生的coroutine对象;
  • 一个由types.coroutine()修饰的生成器,这个生成器可以返回coroutine对象;
  • 一个包含await方法的对象返回的一个迭代器。

案例1:

import asyncio
import time


async def task():
    print(f"{time.strftime('%H:%M:%S')} task 开始 ")
    time.sleep(2)
    print(f"{time.strftime('%H:%M:%S')} task 结束")


coroutine = task()
print(f"{time.strftime('%H:%M:%S')} 产生协程对象 {coroutine},函数并未被调用")
loop = asyncio.get_event_loop()
print(f"{time.strftime('%H:%M:%S')} 开始调用协程任务")
start = time.time()
loop.run_until_complete(coroutine)
end = time.time()
print(f"{time.strftime('%H:%M:%S')} 结束调用协程任务, 耗时{end - start} 秒")

案例2:
为任务绑定回调函数

import asyncio
import time


async def _task():
    print(f"{time.strftime('%H:%M:%S')} task 开始 ")
    time.sleep(2)
    print(f"{time.strftime('%H:%M:%S')} task 结束")
    return "运行结束"


def callback(task):
    print(f"{time.strftime('%H:%M:%S')} 回调函数开始运行")
    print(f"状态:{task.result()}")


coroutine = _task()
print(f"{time.strftime('%H:%M:%S')} 产生协程对象 {coroutine},函数并未被调用")
task = asyncio.ensure_future(coroutine)  # 返回task对象
task.add_done_callback(callback)  # 为task增加一个回调任务
loop = asyncio.get_event_loop()
print(f"{time.strftime('%H:%M:%S')} 开始调用协程任务")
start = time.time()
loop.run_until_complete(task)
end = time.time()
print(f"{time.strftime('%H:%M:%S')} 结束调用协程任务, 耗时{end - start} 秒")

二. 并发

如果需要执行多次协程任务并尽可能的提高效率,这时可以定义一个task列表,然后使用asyncio的wait()方法执行即可。

import asyncio
import time


async def task():
    print(f"{time.strftime('%H:%M:%S')} task 开始 ")
    # 异步调用asyncio.sleep(1):
    await asyncio.sleep(2)
    # time.sleep(2)
    print(f"{time.strftime('%H:%M:%S')} task 结束" )

# 获取EventLoop:
loop = asyncio.get_event_loop()
# 执行coroutine
tasks = [task() for _ in range(5)]
start = time.time()
loop.run_until_complete(asyncio.wait(tasks))
loop.close()
end = time.time()
print(f"用时 {end-start} 秒")

三. 异步请求

先启动一个简单的web服务器

from flask import Flask
import time

app = Flask(__name__)


@app.route('/')
def index():
    time.sleep(3)
    return 'Hello World!'


if __name__ == '__main__':
    app.run(threaded=True)

案例1:请求串行走下来,没有实现挂起。

import asyncio
import requests
import time

start = time.time()


async def request():
    url = 'http://127.0.0.1:5000'
    print(f'{time.strftime("%H:%M:%S")} 请求 {url}')
    response = requests.get(url)
    print(f'{time.strftime("%H:%M:%S")} 得到响应 {response.text}')

tasks = [asyncio.ensure_future(request()) for _ in range(5)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

end = time.time()
print(f'耗时 {end - start} 秒')

使用await将耗时等待的操作挂起,让出控制权。
当协程执行时遇到await, 时间循环就会将本协程挂起,转而去执行别的协程,知道其他的协程挂起或者执行完毕。

案例2:异步IO请求实例
将请求页面的代码封装成一个coroutine对象,在requests中尝试使用await挂起当前执行的I/O.

import asyncio
import requests
import time


async def get(url):
    return requests.get(url)


async def request():
    url = "http://127.0.0.1:5000"
    print(f'{time.strftime("%H:%M:%S")} 请求 {url}')
    response = await get(url)
    print(f'{time.strftime("%H:%M:%S")} 得到响应 {response.text}')


start = time.time()
tasks = [asyncio.ensure_future(request()) for _ in range(5)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
end = time.time()
print(f"耗时 {end - start} 秒")

上面的带动并未达到预期的并发效果。原因是requests不是异步请求,无论如何改封装都无济于事,因此需要找真正的IO请求,aiohttp是一个支持异步请求的库,可以用它和anyncio配合,实现异步请求操作。
案例3:使用aiohttp库

import asyncio
import aiohttp
import time

now = lambda: time.strftime("%H:%M:%S")


async def get(url):
    session = aiohttp.ClientSession()
    response = await session.get(url)
    result = await response.text()
    await session.close()
    return result


async def request():
    url = "http://127.0.0.1:5000"
    print(f"{now()} 请求 {url}")
    result = await get(url)
    print(f"{now()} 得到响应 {result}")


start = time.time()
tasks = [asyncio.ensure_future(request()) for _ in range(5)]
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))

end = time.time()
print(f"耗时 { end - start } 秒")

运行结果符合预期要求,耗时由15秒变成了3秒,实现了并发访问。

将任务数5改成100,运行时间也在3秒多一点,多出来的时间就是I/O时延了。
可见,使用异步协程之后,几乎可以在相同时间内实现成百上千次的网络请求。

相关文章
|
8月前
|
SQL 关系型数据库 数据库
Python SQLAlchemy模块:从入门到实战的数据库操作指南
免费提供Python+PyCharm编程环境,结合SQLAlchemy ORM框架详解数据库开发。涵盖连接配置、模型定义、CRUD操作、事务控制及Alembic迁移工具,以电商订单系统为例,深入讲解高并发场景下的性能优化与最佳实践,助你高效构建数据驱动应用。
930 7
|
8月前
|
数据采集 Web App开发 数据安全/隐私保护
实战:Python爬虫如何模拟登录与维持会话状态
实战:Python爬虫如何模拟登录与维持会话状态
|
8月前
|
Python
Python编程:运算符详解
本文全面详解Python各类运算符,涵盖算术、比较、逻辑、赋值、位、身份、成员运算符及优先级规则,结合实例代码与运行结果,助你深入掌握Python运算符的使用方法与应用场景。
490 3
|
8月前
|
数据处理 Python
Python编程:类型转换与输入输出
本教程介绍Python中输入输出与类型转换的基础知识,涵盖input()和print()的使用,int()、float()等类型转换方法,并通过综合示例演示数据处理、错误处理及格式化输出,助你掌握核心编程技能。
716 3
|
8月前
|
Java 调度 数据库
Python threading模块:多线程编程的实战指南
本文深入讲解Python多线程编程,涵盖threading模块的核心用法:线程创建、生命周期、同步机制(锁、信号量、条件变量)、线程通信(队列)、守护线程与线程池应用。结合实战案例,如多线程下载器,帮助开发者提升程序并发性能,适用于I/O密集型任务处理。
735 0
|
8月前
|
机器学习/深度学习 监控 数据挖掘
Python 高效清理 Excel 空白行列:从原理到实战
本文介绍如何使用Python的openpyxl库自动清理Excel中的空白行列。通过代码实现高效识别并删除无数据的行与列,解决文件臃肿、读取错误等问题,提升数据处理效率与准确性,适用于各类批量Excel清理任务。
708 0
|
10月前
|
Go 调度 Python
Golang协程和Python协程用法上的那些“不一样”
本文对比了 Python 和 Go 语言中协程的区别,重点分析了调度机制和执行方式的不同。Go 的协程(goroutine)由运行时自动调度,启动后立即执行;而 Python 协程需通过 await 显式调度,依赖事件循环。文中通过代码示例展示了两种协程的实际运行效果。
394 7
|
9月前
|
数据采集 网络协议 API
协程+连接池:高并发Python爬虫的底层优化逻辑
协程+连接池:高并发Python爬虫的底层优化逻辑
|
Go Python
使用python实现一个用户态协程
【6月更文挑战第28天】本文探讨了如何在Python中实现类似Golang中协程(goroutines)和通道(channels)的概念。文章最后提到了`wait_for`函数在处理超时和取消操作中的作
342 1
使用python实现一个用户态协程
|
11月前
|
数据采集 监控 调度
干货分享“用 多线程 爬取数据”:单线程 + 协程的效率反超 3 倍,这才是 Python 异步的正确打开方式
在 Python 爬虫中,多线程因 GIL 和切换开销效率低下,而协程通过用户态调度实现高并发,大幅提升爬取效率。本文详解协程原理、实战对比多线程性能,并提供最佳实践,助你掌握异步爬虫核心技术。

推荐镜像

更多