协程的核心概念

协程是一种线程内部的任务调度机制,通过事件循环实现任务的挂起与恢复执行。其核心目标是在单线程内高效处理多任务,避免不必要的CPU等待。

协程与线程的关系

协程运行在线程内部,并非线程间的切换。操作系统无法感知协程的存在,它完全由程序员通过代码实现的任务切换机制控制。协程的设计初衷是减少线程切换的开销。

事件循环的作用

事件循环是协程的核心调度器,主要承担三项职责:

  • 维护任务队列
  • 监听I/O事件
  • 管理协程的挂起与恢复
    asyncio.run()是启动事件循环的标准入口点。

I/O操作识别

在协程中,任何需要等待外部响应的操作都属于I/O操作。这些操作通常以await关键字为标识:

await asyncio.sleep(1)    # 时间等待型I/O
await aiohttp.get(url)    # 网络请求型I/O
await file.read()         # 磁盘读写型I/O

性能优势

创建子进程比创建子线程消耗更多CPU资源。协程通过以下方式优化性能:

  • 单线程内任务切换无需上下文切换
  • 遇到I/O时立即释放CPU资源
  • 仅对I/O密集型任务有效

实现原理

当事件循环将任务交给CPU执行时,若检测到await关键字会立即挂起当前任务。待I/O操作完成后,事件循环会自动恢复该任务的执行。这种机制确保CPU资源始终被有效利用。

协程函数与协程对象

协程函数是通过async关键字修饰的函数,调用协程函数会返回一个协程对象。调用协程函数本身不会执行函数内的代码,而是生成一个待执行的协程对象。

async def work():
    print('work开始工作')
    print('work正在工作.......')
    print('work作结束')
    return '我工作结束了!!!'

协程对象的执行

协程对象需要通过事件循环(如asyncio.run())来执行。asyncio.run()会自动管理协程的执行流程,包括调用send方法并返回结果。

coroutine_obj = work()
asyncio.run(coroutine_obj)

协程与线程的区别

协程是单线程下的并发模型,通过事件循环调度任务。同一时刻CPU只执行一个线程,但协程可以在单个线程内实现任务切换,避免线程切换的开销。

关键点总结

  • 协程函数需用async定义,调用后返回协程对象。
  • 协程对象需通过asyncio.run()或事件循环触发执行。
  • 协程在单线程内实现并发,效率高于多线程。
  • 协程适合I/O密集型任务,避免线程阻塞问题。

协程基本概念

协程是一种轻量级的线程,由用户控制调度,在单线程内实现并发。通过 async/await 语法实现,能够高效处理 I/O 密集型任务。

协程的状态包括:

  • PENDING:刚创建,尚未执行。
  • SUSPENDED:遇到 await 暂停执行。
  • FINISHED:执行完成或抛出异常。

await 关键字的作用

await 用于挂起当前协程的执行,直到等待的对象完成:

  1. 挂起:暂停当前协程,将控制权交还给事件循环。
  2. 等待:事件循环执行 await 后的对象:
    • 若对象不涉及 I/O,事件循环无法切换其他任务。
    • 若对象涉及 I/O(如网络请求、文件读写),事件循环可调度其他任务。
  3. 恢复:对象执行完成后,事件循环恢复挂起的协程,继续执行并返回结果。

I/O 操作与协程的关系

  • I/O 操作:程序与外部设备(如磁盘、网络)交换数据,会导致 CPU 等待。
  • 协程优化:通过 await 标记 I/O 操作点,事件循环可在等待期间切换其他协程,避免 CPU 空转。

代码示例分析

import asyncio

async def study():
    print('开始学习')
    await asyncio.sleep(2)  # 模拟 I/O 操作
    print('学习结束')
    return '学习返回'

async def main():
    print('main开始')
    await study()  # 等待 study() 协程完成
    print('main结束')
    return 'main返回'

res = asyncio.run(main())  # 运行事件循环
print(res)
执行流程
  1. asyncio.run(main()) 启动事件循环,执行 main() 协程。
  2. main() 打印 main开始,随后 await study() 挂起 main(),执行 study()
  3. study() 打印 开始学习await asyncio.sleep(2) 挂起 study(),事件循环等待 2 秒。
  4. study() 恢复后打印 学习结束,返回 学习返回
  5. main() 恢复后打印 main结束,返回 main返回
  6. 最终输出 main返回

关键注意事项

  • await 后仅接受可等待对象:协程对象、TaskFuture 或实现了 __await__ 方法的对象。
  • 事件循环的调度依赖于 I/O 操作。纯计算任务(无 await)会阻塞事件循环。
  • asyncio.run() 是高级接口,负责创建事件循环并运行协程。

异步编程核心概念

asyncio 是 Python 实现异步编程的标准库,基于协程(Coroutine)和事件循环(Event Loop)构建。以下代码演示了关键机制:

async def coro():  # 协程函数定义
    await async_operation()  # 挂起执行点

任务调度原理

asyncio.create_task() 将协程包装为 Task 对象并立即加入事件循环。多个任务通过 await 显式等待时,事件循环会自动调度:

task1 = asyncio.create_task(work(1, 2))  # 立即开始执行
task2 = asyncio.create_task(work(2, 2))  # 并发执行

执行流程控制

await 表达式实现非阻塞等待,当遇到 IO 操作时自动切换上下文。同步代码与异步代码的混合执行需注意:

print('同步输出')  # 立即执行
await asyncio.sleep(1)  # 挂起当前协程

返回值处理机制

协程返回值通过 await 获取,asyncio.run() 返回主协程的最终结果:

result = await task  # 获取单个任务返回值
final_res = asyncio.run(main())  # 获取入口函数返回值

性能测量方法

使用 time.time() 计算异步操作的耗时需注意测量点的位置:

start = time.time()
await task1  # 包含在此范围内的await都会计入耗时
print(time.time() - start)

异步编程核心概念

asyncio 是 Python 的异步 I/O 框架,基于事件循环实现协程并发。示例代码展示了以下关键点:

  • 协程定义:通过 async def 声明协程函数,如 work()main()
  • 可等待对象await 后接协程、Task 或 Future 对象,例如 await asyncio.sleep(delay)
  • 事件循环asyncio.run(main()) 启动事件循环并执行顶层协程。

并发执行机制

asyncio.gather() 用于并发运行多个协程:

res = await asyncio.gather(work(n=1, delay=8), work(n=2, delay=4), work(n=3, delay=2))
  • 所有协程并行执行,总耗时由最长延迟(8秒)决定。
  • 返回值按输入顺序返回列表。

同步与异步对比

注释部分展示了同步调用方式:

res1 = await work(n=1, delay=2)  # 需等待2秒
res2 = await work(n=2, delay=2)  # 再等待2秒
res3 = await work(n=3, delay=2)  # 又等待2秒
  • 同步调用总耗时为各任务延迟之和(6秒)。
  • 异步并发总耗时仅为最慢任务的延迟(8秒)。

调试与性能分析

代码通过 time.time() 记录执行时间:

start = time.time()
# ...异步操作...
print('main结束', time.time()-start)  # 输出总耗时
  • 验证异步并发的效率优势。
  • 实际场景可用于性能基准测试。

返回值处理

协程返回值通过两种方式传递:

  1. 直接通过 return 返回,如 work() 返回字符串。
  2. asyncio.run() 的返回值是顶层协程的返回结果,最终被打印。

注意事项

  • 避免混用阻塞操作(如 time.sleep)与异步代码。
  • 所有异步操作需在协程函数内通过 await 调用。
  • 事件循环由 asyncio.run() 自动管理,通常无需手动创建。

异步编程与同步下载的区别

同步下载代码会阻塞当前线程,必须等待当前图片完全下载完成后才能开始下一张图片的下载。这种模式在I/O密集型任务中效率较低,因为大部分时间都浪费在等待网络响应上。

异步下载利用事件循环和非阻塞I/O操作,可以在等待一个下载完成的同时开始其他下载任务。这种模式特别适合处理大量网络请求,能显著提高程序的吞吐量。

异步编程关键知识点

事件循环(Event Loop)

  • 异步程序的核心,负责调度和执行协程任务
  • 通过asyncio.run()自动创建和管理
  • 可以同时监控多个I/O操作的状态

协程(Coroutine)

  • 使用async def定义的异步函数
  • 通过await表达式暂停执行,直到等待的操作完成
  • 协程对象需要被事件循环调度才能执行

aiohttp库要点

  • ClientSession是异步HTTP客户端的主要接口
  • 需要配合async with上下文管理器使用
  • 响应对象的read()方法也是异步操作,需要await

任务调度

  • asyncio.gather()用于并发运行多个协程
  • 接收多个协程对象作为参数
  • 返回所有协程结果的聚合列表

代码优化建议

异常处理应该更加细致,特别是网络请求可能出现的各种错误。文件名生成方式可以改进,当前截取URL最后10个字符的方法不够可靠。可以考虑使用更完善的临时文件命名方案或保留原始文件名。

性能对比

异步版本在下载多张图片时优势明显:

  • 同步版本是串行执行,总时间=各图片下载时间之和
  • 异步版本是并行执行,总时间≈最慢的单个图片下载时间
  • 当图片数量增加时,异步版本的优势会成倍放大

代码实现:

from idlelib.rpc import response_queue
import requests
def download(url):
    print(f'开始下载')
    response = requests.get(url)
    print(response.content)
    # 保存图片到本地
    with open(url[-10:], 'wb') as f:
        f.write(response.content)
    print(f'下载成功')
def main():
    url_list = [
        'https://pic4.zhimg.com/v2-ff9deaad173974d1f0dca26b8e879299_1440w.jpg',
        #'https://ts2.tc.mm.bing.net/th/id/OIP-C.5AmeaglwRvHzzdbFVQcJ-QHaNK?rs=1&pid=ImgDetMain&o=7&rm=3',
        #'https://tse4.mm.cn.bing.net/th/id/OIP-C.9QuK_KvGcFEJChiNU3nCywHaLH?w=196&h=294&c=7&r=0&o=7&dpr=1.3&pid=1.7&rm=3',
    ]
    for url in url_list:
        download(url)
main()



async def download(session, url):
    print(f'开始下载: {url}')
    try:
        # 发送异步请求
        async with session.get(url) as response:
            if response.status == 200:
                # 读取图片数据
                content = await response.read()
                # 取URL最后10个字符当文件名
                filename = url[-10:]
                # 保存文件
                with open(filename, 'wb') as f:
                    f.write(content)
                print(f'✅ 下载成功: {filename}')
            else:
                print(f'❌ 下载失败,状态码: {response.status}')
    except Exception as e:
        print(f'⚠️  下载出错: {e}')

async def main():
    # 要下载的图片URL列表
    url_list = [
        'https://pic4.zhimg.com/v2-ff9deaad173974d1f0dca26b8e879299_1440w.jpg',
    ]

    # 创建异步会话
    async with aiohttp.ClientSession() as session:
        # 创建协程任务列表
        coroutine_list = [download(session, url) for url in url_list]
        # 并发执行所有任务
        await asyncio.gather(*coroutine_list)

if __name__ == '__main__':
    asyncio.run(main())
    # 1. 创建一个事件循环
    # 2.将收到 的写成对象包装程一个任务交给时间循环
    # 启动时间循环
Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐