Async协程保姆级教学


目录

​编辑

前言

二、Async协程使用步骤

1.导入标准库

2.协程

三、协程的应用场景

1. 网络 IO

2. 数据库 IO

3. 文件 IO

4. 异步任务调度

5. Web 服务

6. 设备和串口 IO

7. 队列和管道

总结


前言

介绍:

Python 协程的概念源于生成器(Generator)。但它通过 asyncio 和事件循环,进一步扩展了生成器的功能,从而支持异步非阻塞操作。允许程序在执行过程中暂停(挂起),然后在需要时恢复运行。与传统的线程或进程并发不同,协程是 单线程内的轻量级任务,由程序自行管理调度(事件循环),而非依赖操作系统,主要用于I/O 密集型如数据库查询,文件读写、网络请求

大白话:

假设你是个厨师,传统线程的做法是,你炒一道菜必须等这道菜全炒完了才能开始下一道,而协程像个聪明的厨师,边等一道菜炖着时,可以顺手切下一道菜的菜,完全不耽误时间。协程的这种“聪明分配时间”的方式,让它成为处理I/O 密集型任务的好帮手,比如数据库查询、文件操作、或者访问网络数据。


一、Async函数是什么?

目前主流实现方式:py3.5+  [async ,await] 方式,本文也是主要介绍这种方式。

普通函数:顺序执行执行完func1,然后执行func2

def func1():
    print("this is func1")


def func2():
    print("this is func2")


if __name__ == "__main__":
    func1()
    func2()

协程函数:

意义:在遇到IO堵塞的时候,不傻傻等待而是选择其他的异步函数去执行,相当于是你小时候边看电视边吃零食。

二、Async协程使用

1.导入标准库

import asyncio

2.async def

        1、什么是事件循环也就是event_loop,可以理解为死循环,用于检测和执行某些代码

        伪代码:

任务列表  = [ 任务1 ,任务2 ,...]


while True:
    可执行列表,已完成列表 = 去任务列表中检查所有的任务,将可执行和已完成的任务返回
    
    
    
    for 就绪任务 in 可执行的任务列表:
        执行就绪任务
        
    for 已完成任务 in 已完成的任务列表:
        在任务列表中移除已完成的任务
    
    如果所有的任务都已经完成了 则终止循环

协程函数定义:async def 函数名,协程对象,执行协程函数()得到的协程对象

import asyncio


async def func():
    pass


# 返回协程对象,内部函数不会执行,必须使用循环事件调用
result = func()


asyncio.run(result)

# py3.7 引入asyncio.run(协程对象) 等于之前版本的
# loop = asyncio.get_event_loop()
# loop.run_until_complete(result)

3.await

等待对应值得到结果之后继续

await后面只能跟可等待对象 such as [ 协程对象,Feature,Task对象 ] 可以跟多个

import asyncio


async def main():

    await join_worker()
    print("main function")


async def join_worker():
    print("123")


if __name__ == "__main__":
    asyncio.run(main())

4.Task对象 py3.7+

作用:往事件循环中添加任务

  • 封装协程并在后台执行:通过 asyncio.create_task() 或 loop.create_task() 等方式,可以将一个协程封装为一个 Task 对象,使得它在后台运行,且不阻塞主程序的执行。

  • 并发执行Task 对象使得多个协程可以并行执行,而无需手动管理协程的调度。它允许我们在事件循环中启动多个协程任务,并等待它们完成。

  • 获取结果Task 对象可以用于等待协程的执行结果。当我们通过 await task 或 task.result() 获取结果时,Task 会返回协程的返回值。

  • 取消任务:通过 task.cancel(),可以取消一个正在执行的任务。如果任务已经完成或取消,调用 task.cancel() 不会抛出异常。

  • 处理异常:如果协程抛出异常,Task 对象可以捕获这个异常,允许在后续处理或显示错误信息。

例子一:用到不多

import asyncio


async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"


async def main():
    task1 = asyncio.create_task(func())
    task2 = asyncio.create_task(func())

    # 放入事件循环中但是并不执行
    print("mian 函数结束")

    ret1 = await task1
    ret2 = await task2

    print(ret1, ret2)


asyncio.run(main=main())

# > python async_main.py
# mian 函数结束
# 1
# 1
# 2
# 2
# 返回值 返回值

例子二:用到比较多

import asyncio


async def func():
    print(1)
    await asyncio.sleep(2)
    print(2)
    return "返回值"


async def main():
    
    
    print("函数开始")
    
    await asyncio.gather(func(),func())




asyncio.run(main=main())

# > python async_main.py
# mian 函数结束
# 1
# 1
# 2
# 2
# 返回值 返回值

5.Feature对象

(基本用不到,理解即可)

Feature 对象(实际上是 concurrent.futures.Future 或 asyncio.Future)是一个用于表示异步操作结果的容器。它的主要作用是提供一个可以在未来获取异步操作结果的机制,常用于任务管理和协作。

import asyncio

async def set_future_result(future: asyncio.Future):
    print("正在设置 Future 的结果...")
    await asyncio.sleep(2)  # 模拟异步操作
    future.set_result("操作成功!")  # 设置结果

async def main():
    # 创建一个 Future 对象
    future = asyncio.Future()

    # 启动任务
    asyncio.create_task(set_future_result(future))

    # 等待 Future 完成并获取结果
    result = await future
    print(f"Future 结果: {result}")

asyncio.run(main())

三、进程池和线程池

在 Python 中,进程池线程池是用来并发执行任务的两种工具,它们都能显著提升程序在 CPU 密集型或 I/O 密集型任务中的效率。

1. 进程池

概念

  • 进程池使用多个进程来执行任务,每个进程都有独立的内存空间。
  • 适合 CPU 密集型任务,如数学计算、大型数据处理,因为 Python 的 GIL (Global Interpreter Lock) 在多进程中不会成为瓶颈。

使用场景

  • 多核计算任务。
  • 需要隔离内存的任务。
  • 不需要共享状态的任务。
from multiprocessing import Pool
import os

def task(n):
    print(f"Process ID: {os.getpid()} -> Task {n}")
    return n * n

if __name__ == "__main__":
    with Pool(processes=4) as pool:  # 创建一个拥有 4 个进程的进程池
        results = pool.map(task, range(10))  # 并发运行任务
    print("Results:", results)



Process ID: 12345 -> Task 0
Process ID: 12346 -> Task 1
...
Results: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

2. 线程池

概念

  • 线程池使用多个线程来执行任务,共享同一个进程的内存空间。
  • 适合 I/O 密集型任务,如文件操作、网络请求,因为线程切换的开销较低。

使用场景

  • 网络爬虫或 HTTP 请求。
  • 文件读写。
  • 数据库操作。

示例代码

使用 concurrent.futures.ThreadPoolExecutor 创建线程池。

from concurrent.futures import ThreadPoolExecutor
import threading

def task(n):
    print(f"Thread ID: {threading.get_ident()} -> Task {n}")
    return n * n

if __name__ == "__main__":
    with ThreadPoolExecutor(max_workers=4) as executor:  # 创建一个拥有 4 个线程的线程池
        results = list(executor.map(task, range(10)))  # 并发运行任务
    print("Results:", results)


Thread ID: 140700236138240 -> Task 0
Thread ID: 140700227745536 -> Task 1
...
Results: [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

3. 进程池 vs. 线程池

特性 进程池 线程池
资源使用 每个进程有独立内存,消耗更多资源。 所有线程共享内存,资源消耗较少。
适用任务类型 CPU 密集型任务(计算、大数据处理)。 I/O 密集型任务(网络、文件操作)。
GIL 影响 不受 GIL 影响,多核 CPU 能完全利用。 受 GIL 影响,同一时刻只能有一个线程执行字节码。
切换开销 进程间上下文切换开销较大。 线程间上下文切换开销较小。
状态共享 进程间不共享状态。 线程间可以共享状态。
错误隔离 进程崩溃不会影响其他进程。 一个线程崩溃可能影响整个进程。

四、如何结合协程和进程和线程(重点)

代码可能会存在交叉事件,比如项目是基于协程异步编程的,但是mysql一些版本不支持async,支持进程和线程编程

用法场景,异步编程场景下,第三方模块不支持基于协程的异步,用run_in_executor来解决问题

例子

import time
import concurrent.futures
import asyncio

# 定义一个普通的同步函数,用于模拟耗时操作
def func1():
    time.sleep(1)  # 模拟任务运行1秒
    return "普通函数"  # 返回结果

# 定义主异步函数
async def main():
    # 获取当前事件循环(Asyncio 的核心,用于管理任务的执行)
    loop = asyncio.get_running_loop()

    # 使用默认线程池(事件循环自带的线程池)执行同步函数
    # run_in_executor 方法将同步函数 func1 放入线程池执行
    fut = loop.run_in_executor(None, func1)  # None 表示使用默认线程池

    # 等待线程池中的任务完成并获取结果
    result = await fut
    print("默认的池子:", result)  # 输出结果

    # 显式创建一个线程池执行器(自定义线程池)
    with concurrent.futures.ThreadPoolExecutor() as pool:
        # 使用自定义线程池执行同步函数 func1
        # 将线程池 pool 传递给 run_in_executor 方法
        result = await loop.run_in_executor(pool, func=func1)
        print("线程池子", result)  # 输出线程池执行结果

    # 显式创建一个进程池执行器(自定义进程池)
    with concurrent.futures.ProcessPoolExecutor() as pool:
        # 使用自定义进程池执行同步函数 func1
        # 将进程池 pool 传递给 run_in_executor 方法
        result = await loop.run_in_executor(pool, func1)
        print("进程池子", result)  # 输出进程池执行结果

# 使用 asyncio.run() 运行顶级异步函数 main
# asyncio.run 会初始化事件循环并执行 main 函数
asyncio.run(main=main())

实战案例:

import asyncio
import requests


# 定义一个异步函数,用于从 URL 下载图片
async def get_jpg(url: str):
    # 获取当前事件循环
    loop: asyncio.AbstractEventLoop = asyncio.get_running_loop()

    # 在默认线程池中执行 requests.get 请求
    result: asyncio.Future[requests.Response] = loop.run_in_executor(
        None, requests.get, url
    )

    # 等待结果完成
    response = await result

    # 从 URL 提取文件名并保存图片
    url_name = url.split("/")[-1]
    with open(f"{url_name}.jpg", mode="wb") as file:
        file.write(response.content)

    # 返回下载的文件名,避免返回 None
    return url_name


# 定义一个异步函数,用于并发下载多个图片
async def download_images(urls: list):
    tasks = [get_jpg(url) for url in urls]
    results = await asyncio.gather(*tasks)  # 等待所有任务完成
    print(f"Downloaded files: {results}")


# 主入口
if __name__ == "__main__":
    # 定义图片 URL 列表
    urls = [
        "https://example.com/image1.jpg",
        "https://example.com/image2.jpg",
        "https://example.com/image3.jpg",
    ]

    # 使用 asyncio.run 运行主任务
    asyncio.run(download_images(urls))

五、协程的应用场景

1. 网络 IO

网络 IO 是 Python 异步最常见的应用场景,得益于其非阻塞特性,可以大幅提升高并发处理能力。

  • 场景:
    • HTTP 请求与响应:如调用 REST API、爬取网页数据。
    • WebSocket 通信:实时数据传输(如聊天系统、股票行情)。
    • Socket 编程:自定义协议的客户端和服务器通信。
    • 文件上传与下载:高效处理文件的并发传输。
  • 库:
    • aiohttp(HTTP 客户端/服务器)
    • websockets(WebSocket 通信)
    • asyncio 的 stream API(底层 Socket)

示例:HTTP 请求并发

import aiohttp
import asyncio

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()

async def main():
    urls = ["https://example.com", "https://python.org"]
    results = await asyncio.gather(*(fetch(url) for url in urls))
    print(results)

asyncio.run(main())

2. 数据库 IO

异步数据库操作能够在等待查询返回时,不阻塞其他任务。

  • 场景:
    • 高并发的数据库查询。
    • 实时更新、插入大量数据。
    • 批量读取和导出大规模数据。
  • 库:
    • asyncpg(PostgreSQL)
    • aiomysql(MySQL)
    • motor(MongoDB 异步驱动)
    • SQLAlchemy 的异步支持

示例:PostgreSQL 查询

import asyncpg
import asyncio

async def query_db():
    conn = await asyncpg.connect('postgresql://user:password@localhost/dbname')
    rows = await conn.fetch("SELECT * FROM users")
    print(rows)
    await conn.close()

asyncio.run(query_db())

3. 文件 IO

虽然文件 IO 本质上是阻塞的,但可以通过 aiofiles 实现异步化,从而避免阻塞主协程。

  • 场景:
    • 异步读取或写入大文件。
    • 并发处理多个文件。
    • 实时日志写入。
  • 库:
    • aiofiles

示例:异步读取文件

import aiofiles
import asyncio

async def read_file(file_path):
    async with aiofiles.open(file_path, mode='r') as file:
        contents = await file.read()
        print(contents)

asyncio.run(read_file("example.txt"))

4. 异步任务调度

协程可以用于处理 IO 密集型任务,同时支持定时任务和多任务调度。

  • 场景:
    • 同时处理多个 IO 请求。
    • 实现定时任务(如延迟、周期性执行)。
    • 合并多个异步操作的结果。
  • 库:
    • 标准库 asyncio

示例:多任务调度

import asyncio

async def task(name, delay):
    await asyncio.sleep(delay)
    print(f"Task {name} completed")

async def main():
    await asyncio.gather(
        task("A", 2),
        task("B", 1),
        task("C", 3)
    )

asyncio.run(main())

5. Web 服务

异步 Web 框架使得构建高性能 Web 服务变得简单。

  • 场景:
    • 实现高并发的 REST API 服务。
    • 实时推送通知(如 SSE 或 WebSocket)。
    • 数据流处理(如视频流、文件流)。
  • 库:
    • FastAPI
    • Sanic

示例:FastAPI 异步接口

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
async def read_root():
    return {"message": "Hello, Async World!"}

6. 设备和串口 IO

异步串口通信允许与物联网设备进行高效数据交换。

  • 场景:
    • 实时采集传感器数据。
    • 控制物联网设备。
    • 多设备并发通信。
  • 库:
    • pyserial-asyncio

示例:异步串口读取

import asyncio
import serial_asyncio

async def read_from_device():
    reader, writer = await serial_asyncio.open_serial_connection(url='/dev/ttyUSB0', baudrate=9600)
    while True:
        data = await reader.read(100)
        print(f"Received: {data}")

asyncio.run(read_from_device())

7. 队列和管道

异步队列用于实现生产者-消费者模式或任务分发。

  • 场景:
    • 多协程之间共享数据。
    • 实现高效的任务处理管道。
  • 库:
    • asyncio.Queue

示例:生产者-消费者

import asyncio

async def producer(queue):
    for i in range(5):
        await asyncio.sleep(1)
        await queue.put(i)
        print(f"Produced: {i}")

async def consumer(queue):
    while True:
        item = await queue.get()
        if item is None:
            break
        print(f"Consumed: {item}")
        queue.task_done()

async def main():
    queue = asyncio.Queue()
    await asyncio.gather(producer(queue), consumer(queue))

asyncio.run(main())

1、异步Mysql

1、连接mysql异步会导致io请求

pip install aiomysql
import asyncio
import aiomysql


# 异步操作的函数
async def execute():
    # 使用 async with 管理连接和游标的生命周期
    async with aiomysql.connect(
        host="127.0.0.1", port=3306, user="root", password="root", db="pythontest"
    ) as conn:
        # 使用 async with 管理游标
        async with conn.cursor() as cur:
            # 执行查询操作
            await cur.execute("SELECT * FROM student")

            # 获取所有查询结果
            result = await cur.fetchall()
            print("查询结果:", result)

            # 如果查询无结果,可以处理默认值或空情况
            if not result:
                print("没有数据")


# 主程序入口
asyncio.run(execute())

2、fastapi的异步框架

pip install fastapi
pip install uvicorn
import uvicorn

from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def index():
    # 某个io耗时10s 意味着第一个请求等待完之后,第二个请求才能执行
    return {"message": "hello world"}


@app.get("/red")
async def red():
    # 遇到io请求等待时候马上接第二个请求
    return {"message": "async red"}


if __name__ == "__main__":

    uvicorn.run(app=app, host="127.0.0.1", port=5000)

3.爬虫例子

import asyncio
import aiohttp

async def fetch_url(url: str):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            # 获取响应内容
            data = await response.text()
            print(f"Response from {url}: {data[:100]}...")  # 只打印前100个字符

# 主入口函数
async def main():
    url = "https://jsonplaceholder.typicode.com/todos/1"
    await fetch_url(url)

# 运行异步主函数
asyncio.run(main())

总结:

1. asyncio.run()

asyncio.run() 是一个用来执行异步函数的顶级方法。它会启动一个事件循环,运行给定的协程,并在协程执行完毕后关闭事件循环。

2. async def 和 await

  • async def:定义一个异步函数,这个函数返回一个协程对象,而不是普通的返回值。
  • await:用于等待一个异步操作的结果。它会暂停协程,直到被 await 的任务完成,随后继续执行。

3. asyncio.create_task()

asyncio.create_task() 用于将协程包装成任务并安排执行。它允许你在事件循环中并发执行多个任务。

4. asyncio.gather()

asyncio.gather() 用于并发执行多个任务并等待它们的结果。它返回一个包含所有任务结果的列表。

5. asyncio.sleep()

asyncio.sleep() 是异步的 sleep 函数,它不会阻塞事件循环,而是让出控制权,允许其他任务执行。

6. asyncio.Event

asyncio.Event() 是一个同步原语,用于在线程或协程之间传递信号。事件初始为“未设置”(not set),通过调用 set() 方法将其设为“已设置”(set),其他协程可以通过 wait() 方法等待该事件的设置。

7. asyncio.Lock

asyncio.Lock 是一个用于协程同步的锁。它用于防止多个协程同时访问共享资源。

8. asyncio.Semaphore

asyncio.Semaphore 是一个限制并发访问资源的工具。它维护一个计数器,允许最多 n 个协程同时访问资源。

9. asyncio.Future

asyncio.Future 是一个表示异步操作的结果的对象,它通常用于一些协程间的通信。协程可以通过 Future 传递信息,并在需要时等待其完成。

10. asyncio.Queue

asyncio.Queue 是一个异步队列,用于在协程之间传递数据,特别适用于生产者-消费者模式。

你可能感兴趣的:(python,大数据,python,开发语言,协程,async)