python协程实现高并发的技术详解

2025-11-21 18:50

本文主要是介绍python协程实现高并发的技术详解,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《python协程实现高并发的技术详解》协程是实现高并发的一种非常高效的方式,特别适合处理大量I/O操作的场景,本文我们将简单介绍python协程实现高并发的相关方法,需要的小伙伴可以了解下...

协程是实现高并发的一种非常高效的方式,特别适合处理大量I/O操作(如网络请求、文件操作)的场景。它通过在单个线程内实现多个任务的切换来避免阻塞,从而最大限度地利用CPU资源。

核心概念与简单示例

要理解协程如何实现高并发,首先要明白几个关键概念:

  • 协程(Coroutine):一种轻量级的“微线程”,可以在执行过程中暂停(await)并在适当的时候恢复,而不是像普通函数那样一次性执行完毕。这种可挂起的能力是高并发的基石。
  • 事件循环(Event Loop):这是协程的“大脑”或调度中心。它负责在单个线程中调度和执行多个协程任务。当一个协程因等待I/O(比如网络响应)而暂停时,事件循环会立刻切换到另一个就绪的协程去执行,从而避免了CPU的空等。
  • async/await 关键字:async 用于声明一个函数是异步函数(即协程),await 用于在协程内部挂起自身,直到其后面的异步操作完成。

下面是一个简单的例子,演示了如何使用 asyncio 库创建两个协程,让它们“并发”地执行:

import asyncio
import time

async def say_after(delay, what):
    await asyncio.sleep(delay)  # 模拟一个耗时的I/O操作
    print(what)

async def main():
    print(f"程序开始于: {time.strftime('%X')}")

    # 使用 asyncio.gather 来并发运行两个协程
    await asyncio.gather(
        say_after(2, 'Hello,'),
        say_after(1, 'World!')
    )

    print(f"程序结束于: {time.strftime('%X')}")

# 运行主协程
asyncio.run(main())

在这个例子中,虽然 Hello, 任务设定等待2秒,而 World! 任务只等待1秒,但由于它们是并发执行的,总耗时大约只有2秒,而不是顺序执行时的3秒。输出将会是:

程序开始于: 12:00:00
World!
Hello,
程序结束于: 12:00:02

高并发实践:网络请求

协程的真正威力体现在处理成千上万个网络请求时。下面的例子展示了如何使用 aiohttp 库异步地并发获取多个网页内容。

import asyncio
import aiohttp
import time

async def fetch_url(session, url):
    async with session.get(url) as javascriptresponse:
China编程        # 注意:在实际项目中,对于大响应体,应使用 response.content.read(chunk_size) 流式读取
        html = await response.text()
        print(f"从 {url} 获取了 {len(html)} 个字符")

async def main():
    urls = [
        'https://www.pythttp://www.chinasem.cnhon.org',
        'https:/android/www.example.com',
        'https://httpbin.org/get',
        # ... 可以添加更多URL
    ]

    # 关键:在整个抓取会话期间,复用同一个 ClientSession 实例
    # 其内部维护了一个连接池,可以复用TCP连接,极大提升性能
    async with aiohttp.ClientSession() as session:
        tasks = []
        for url in urls:
            # 为每个URL创建一个协程任务
            task = asyncio.create_task(fetch_url(session, url))
            tasks.append(task)

        # 等待所有任务完成
        await asyncio.gather(*tasks)

# 记录耗时
start_time = time.time()
asyncio.run(main())
end_time = time.time()
print(f"总共耗时: {end_time - start_time:.2f} 秒")

这个程序几乎在同一时间发起所有请求,总耗时基本等于最慢的那个请求的耗时,而不是所有请求耗时的总和。

协程如何实现高并发:核心技术

协程实现高并发主要依赖于以下两点:

协作式多任务与事件循环

协程是协作式的,这意味着它们会主动在可能发生阻塞的地方(即 await 处)挂起自己,将控制权交还给事件循环。事件循环则不断地检查:有哪些I/O操作已经完成了?哪些协程可以恢复了?然后调度执行那些就绪的协程。这个过程在单线程内完成,完全避免了多线程带来的锁竞争和上下文切换的开销。

非阻塞I/O与连接池

协程需要与非阻塞的I/O系统调用配合才能发挥最大效能。像 aiohttp 这样的库在底层使用了操作系统的非阻塞I/O机制(如linux的 epoll)。当一个协程发起网络请求时,这个请求会以非阻塞方式立即发出,然后协程挂起。事件循环则通过I/O多路复用机制(如 select, poll, epoll)来监听大量socket(网络连接)的状态,一旦某个socket的数据准备好了,事件循环就会唤醒等待该socket的协程继续执行。同时,如第一个搜索结果强调的,连接池(Connection Pool) 也至关重要。通过复用已经建立的TCP连接,可以避免为每个请求都进行耗时的TCP三次握手,这在高并发下能带来数量级的性能提升。

协程、多线程与多进程的对比

理解协程的适用场景很重要,下面的表格对比了三种主要的并发方式:

特性协程 (Coroutine)多线程 (Threading)多进程 (Multiprocessing)
运行模型单线程内协作式切换操作系统线程,抢占式调度独立的操作系统进程
数据共享共享内存,无需加锁(但需注意任务内状态)共享内存,需处理线程安全(如锁)内存不共享,需通过IPC(如队列)
开销极小,可轻松创建数万协程较大,线程数android受限于内存和上下文切换成本巨大,进程数通常与CPU核数相关
最佳场景I/O密集型任务(网络、文件读写)I/O密集型任务(但协程通常更高效)CPU密集型任务(计算、图像处理)
编程复杂度中等(需理解 async/await)高(需处理复杂的同步问题)中高(需处理进程间通信)

简单来说:

I/O密集型(任务大部分时间在等待):协程 >> 多线程 > 多进程

CPU密集型(任务大部分时间在计算):多进程 > 多线程/协程(由于python的GIL,计算密集型任务在多线程中无法真正并行)

实践建议与常见误区

避免在协程中调用阻塞性代码:不要在协程内使用 time.sleep(1) 或同步的 requests.get()。这会阻塞整个事件循环,使并发失效。务必使用对应的异步版本 await asyncio.sleep(1) 和 aiohttp。

使用信号量(Semaphore)控制并发度:虽然协程很轻量,但向同一个目标服务器发起无限度的并发请求可能会被视为攻击或导致对方服务不可用。可以使用 asyncio.Semaphore 来限制最大并发数。

复用对象:如网络请求的例子所示,在整个应用生命周期内复用 ClientSession 这样的对象,以利用连接池。

组合使用多种并发模型:对于更复杂的场景,可以采用“多进程+协程”的混合模式。例如,用多进程利用多核CPU,在每个进程内部使用协程处理高并发的I/O。

到此这篇关于python协程实现高并发的技术详解的文章就介绍到这了,更多相关python协程高并发内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持China编程(www.chinasem.cn)!

这篇关于python协程实现高并发的技术详解的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1156233

相关文章

C#借助Spire.XLS for .NET实现在Excel中添加文档属性

《C#借助Spire.XLSfor.NET实现在Excel中添加文档属性》在日常的数据处理和项目管理中,Excel文档扮演着举足轻重的角色,本文将深入探讨如何在C#中借助强大的第三方库Spire.... 目录为什么需要程序化添加Excel文档属性使用Spire.XLS for .NET库实现文档属性管理Sp

C++ move 的作用详解及陷阱最佳实践

《C++move的作用详解及陷阱最佳实践》文章详细介绍了C++中的`std::move`函数的作用,包括为什么需要它、它的本质、典型使用场景、以及一些常见陷阱和最佳实践,感兴趣的朋友跟随小编一起看... 目录C++ move 的作用详解一、一句话总结二、为什么需要 move?C++98/03 的痛点⚡C++

Python+FFmpeg实现视频自动化处理的完整指南

《Python+FFmpeg实现视频自动化处理的完整指南》本文总结了一套在Python中使用subprocess.run调用FFmpeg进行视频自动化处理的解决方案,涵盖了跨平台硬件加速、中间素材处理... 目录一、 跨平台硬件加速:统一接口设计1. 核心映射逻辑2. python 实现代码二、 中间素材处

MySQL中between and的基本用法、范围查询示例详解

《MySQL中betweenand的基本用法、范围查询示例详解》BETWEENAND操作符在MySQL中用于选择在两个值之间的数据,包括边界值,它支持数值和日期类型,示例展示了如何使用BETWEEN... 目录一、between and语法二、使用示例2.1、betwphpeen and数值查询2.2、be

python中的flask_sqlalchemy的使用及示例详解

《python中的flask_sqlalchemy的使用及示例详解》文章主要介绍了在使用SQLAlchemy创建模型实例时,通过元类动态创建实例的方式,并说明了如何在实例化时执行__init__方法,... 目录@orm.reconstructorSQLAlchemy的回滚关联其他模型数据库基本操作将数据添

Java数组动态扩容的实现示例

《Java数组动态扩容的实现示例》本文主要介绍了Java数组动态扩容的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录1 问题2 方法3 结语1 问题实现动态的给数组添加元素效果,实现对数组扩容,原始数组使用静态分配

Java中ArrayList与顺序表示例详解

《Java中ArrayList与顺序表示例详解》顺序表是在计算机内存中以数组的形式保存的线性表,是指用一组地址连续的存储单元依次存储数据元素的线性结构,:本文主要介绍Java中ArrayList与... 目录前言一、Java集合框架核心接口与分类ArrayList二、顺序表数据结构中的顺序表三、常用代码手动

Python实现快速扫描目标主机的开放端口和服务

《Python实现快速扫描目标主机的开放端口和服务》这篇文章主要为大家详细介绍了如何使用Python编写一个功能强大的端口扫描器脚本,实现快速扫描目标主机的开放端口和服务,感兴趣的小伙伴可以了解下... 目录功能介绍场景应用1. 网络安全审计2. 系统管理维护3. 网络故障排查4. 合规性检查报错处理1.

JAVA线程的周期及调度机制详解

《JAVA线程的周期及调度机制详解》Java线程的生命周期包括NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING和TERMINATED,线程调度依赖操作系统,采用抢占... 目录Java线程的生命周期线程状态转换示例代码JAVA线程调度机制优先级设置示例注意事项JAVA线程

Python轻松实现Word到Markdown的转换

《Python轻松实现Word到Markdown的转换》在文档管理、内容发布等场景中,将Word转换为Markdown格式是常见需求,本文将介绍如何使用FreeSpire.DocforPython实现... 目录一、工具简介二、核心转换实现1. 基础单文件转换2. 批量转换Word文件三、工具特性分析优点局