讲师中心 微信公众号
AI工具推荐 视频效率加速

解析Python多进程API调用慢的原因及优化策略

大辰姑娘_7446

大辰姑娘_7446

发布时间:2025-11-09 13:16:01

|

507人浏览过

|

来源于php中文网

原创

解析python多进程api调用慢的原因及优化策略

本文深入探讨了Python中多进程处理网络API请求时可能出现的性能瓶颈,特别是当多进程方案反而比单进程更慢的原因。我们将分析进程创建、进程间通信(IPC)的开销,并提出针对I/O密集型任务的优化策略,包括使用`multiprocessing.Pool`、`requests.Session`,以及探讨多线程和异步编程作为更高效的替代方案。

1. 理解多进程在I/O密集型任务中的挑战

在处理需要从Web API拉取数据的场景中,开发者常倾向于使用多进程来加速数据获取。然而,有时会发现多进程方案反而比单进程方案耗时更长,这通常是由于对并发模型选择和其底层开销理解不足所致。

1.1 进程创建与销毁的开销

创建新的操作系统进程是一项资源密集型操作。它涉及到内存分配、复制父进程的数据结构(在某些操作系统上,如Linux,会使用写时复制技术),以及为新进程设置执行环境。当每个任务都需要创建一个新进程时,这些重复的创建和销毁开销会迅速累积,吞噬掉并行处理带来的潜在收益。在初始的慢速多进程代码中,为每个API请求手动创建Process实例,正是导致性能下降的一个主要原因。

1.2 进程间通信(IPC)的代价

多进程之间无法直接共享内存,需要通过进程间通信(IPC)机制来交换数据,例如使用Queue。数据在发送方需要被序列化(如Python的pickle模块),通过管道或共享内存传输到接收方,再进行反序列化。如果传输的数据量大或结构复杂,序列化和反序列化的过程将带来显著的CPU和内存开销,这进一步降低了整体性能。原始多进程代码中使用Queue来收集每个进程的API响应,也增加了不必要的IPC负担。

立即学习“Python免费学习笔记(深入)”;

1.3 I/O密集型与CPU密集型任务的区分

理解任务类型是选择正确并发模型的关键。

  • CPU密集型任务: 任务主要时间花费在CPU计算上,如复杂的数学运算、图像处理等。Python的全局解释器锁(GIL)限制了单个进程内同一时刻只有一个线程执行Python字节码。因此,对于CPU密集型任务,多进程是绕过GIL限制,充分利用多核CPU的有效方式。
  • I/O密集型任务: 任务主要时间花费在等待外部资源响应上,如网络请求、文件读写、数据库查询等。在等待期间,CPU是空闲的。API请求就是典型的I/O密集型任务,大部分时间都在等待网络响应。

对于I/O密集型任务,多进程的优势并不明显,因为进程在等待I/O时,其他进程同样可能在等待I/O。而进程创建和IPC的开销,反而可能使得多进程的效率低于单进程。

2. 优化策略与最佳实践

针对I/O密集型API请求的场景,我们可以采用以下优化策略:

2.1 使用multiprocessing.Pool简化进程管理

multiprocessing.Pool提供了一种更高级、更高效的方式来管理一组工作进程。它会在启动时创建固定数量的进程,并将任务分发给这些进程,从而摊销了进程创建的开销,避免了频繁的进程创建与销毁。

Li Python Sec Check
Li Python Sec Check

Python 安全规范检查工具:基于 CloudBase 规范、腾讯安全指南,LLM 智能分析(默认禁用,优先本地执行)

下载

以下是使用multiprocessing.Pool优化API请求的示例代码:

from multiprocessing import Pool
import requests
import time

# 假设这是一个模拟的API请求函数
def pull_data(row_data):
    """
    模拟从Web API拉取数据的函数。
    row_data 可以是序列号、ID或其他请求参数。
    """
    url_api = 'https://jsonplaceholder.typicode.com/posts/1' # 替换为实际的API地址
    # 实际应用中,post_json应根据row_data构建
    # 这里为了示例简化,假设row_data直接是post_json的一部分或用于构建它
    # 或者直接使用GET请求,如果API支持
    try:
        # 模拟复杂的查询,可能需要1.5秒
        # time.sleep(1.5)
        # 实际的POST请求
        post_json = {"data": row_data} # 假设row_data是字典或可序列化对象
        x = requests.post(url_api, json=post_json) # 使用json参数发送JSON数据
        x.raise_for_status() # 检查HTTP请求是否成功
        return x.json()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching data for {row_data}: {e}")
        return None

def optimized_api_requests(rows_list, max_workers=5):
    """
    使用multiprocessing.Pool并行处理API请求。
    """
    start_time = time.time()
    with Pool(processes=max_workers) as pool:
        # pool.map 会将rows_list中的每个元素作为参数传递给pull_data函数
        # 并并行执行,然后收集所有结果。
        results = pool.map(pull_data, rows_list)
    end_time = time.time()
    print(f"\n所有请求完成,耗时: {format(end_time - start_time, '.2f')}秒")
    return results

if __name__ == '__main__':
    # 模拟一系列需要请求的数据行
    sample_rows = [f"SN_{i}" for i in range(10)] # 假设有10个请求
    print("开始使用 multiprocessing.Pool 进行API请求...")
    api_results = optimized_api_requests(sample_rows, max_workers=3) # 设置3个工作进程

    # 打印部分结果
    for i, res in enumerate(api_results):
        if res:
            print(f"请求 {sample_rows[i]} 结果示例: {res['id'] if 'id' in res else res}")
        else:
            print(f"请求 {sample_rows[i]} 失败。")
        if i >= 2: # 只打印前3个结果
            break

代码说明:

  • Pool(processes=max_workers)创建了一个进程池,max_workers指定了并发进程的数量。
  • pool.map(pull_data, rows_list)将rows_list中的每个元素作为参数,依次调用pull_data函数,并在池中的进程中并行执行。map函数会等待所有任务完成并返回一个结果列表。

2.2 利用requests.Session提高网络请求效率

在进行大量HTTP请求时,每次请求都建立新的TCP连接(包括DNS解析、TCP三次握手、SSL/TLS握手等)会带来显著的开销。requests.Session对象可以跨多个请求重用TCP连接,从而减少这些重复的开销,尤其是在与同一个API服务器进行多次交互时效果显著。

为了在多进程/多线程环境中有效利用requests.Session,通常的做法是在每个工作进程或线程内部创建一个Session实例,而不是在全局共享一个。

import requests
import time
from multiprocessing import Pool, current_process

# 为每个进程创建一个Session
# 注意:requests.Session对象不是线程安全的,也不是进程安全的,
# 所以每个worker(进程或线程)都应该有自己的Session实例。
def pull_data_with_session(row_data):
    """
    使用requests.Session从Web API拉取数据的函数。
    每个进程(或线程)应该有自己的Session。
    """
    # 在每个进程的执行上下文中创建Session
    # 对于Pool,这个函数会在每个worker进程中被调用
    session = requests.Session()
    url_api = 'https://jsonplaceholder.typicode.com/posts/1'
    try:
        post_json = {"data": row_data, "process": current_process().name}
        x = session.post(url_api, json=post_json)
        x.raise_for_status()
        return x.json()
    except requests.exceptions.RequestException as e:
        print(f"Error fetching data for {row_data} in {current_process().name}: {e}")
        return None
    finally:
        session.close() # 显式关闭Session,释放资源

def optimized_api_requests_with_session(rows_list, max_workers=5):
    start_time = time.time()
    with Pool(processes=max_workers) as pool:
        results = pool.map(pull_data_with_session, rows_list)
    end_time = time.time()
    print(f"\n所有请求(带Session)完成,耗时: {format(end_time - start_time, '.2f')}秒")
    return results

if __name__ == '__main__':
    sample_rows = [f"SN_{i}" for i in range(10)]
    print("\n开始使用 multiprocessing.Pool 和 requests.Session 进行API请求...")
    api_results_session = optimized_api_requests_with_session(sample_rows, max_workers=3)

    for i, res in enumerate(api_results_session):
        if res:
            print(f"请求 {sample_rows[i]} 结果示例: {res.get('id')} (由 {res.get('process')} 处理)")
        else:
            print(f"请求 {sample_rows[i]} 失败。")
        if i >= 2:
            break

2.3 针对I/O密集型任务选择合适的并发模型

对于I/O密集型任务,除了multiprocessing.Pool,更推荐使用以下两种并发模型:

  • 多线程(Multithreading): Python的GIL对CPU密集型任务有性能限制,但对I/O密集型任务影响较小。当一个线程在等待I/O时,GIL会被释放,允许其他线程执行。因此,多线程是处理I/O密集型任务的有效且开销较小的方案。concurrent.futures.ThreadPoolExecutor是Python标准库中用于管理线程池的强大工具。

    from concurrent.futures import ThreadPoolExecutor
    import requests
    import time
    
    def pull_data_threaded(row_data):
        session = requests.Session() # 每个线程创建自己的Session
        url_api = 'https://jsonplaceholder.typicode.com/posts/1'
        try:
            post_json = {"data": row_data, "thread": current_process().name} # 线程也可以获取当前进程名
            x = session.post(url_api, json=post_json)
            x.raise_for_status()
            return x.json()
        except requests.exceptions.RequestException as e:
            print(f"Error fetching data for {row_data}: {e}")
            return None
        finally:
            session.close()
    
    def threaded_api_requests(rows_list, max_workers=10):
        start_time = time.time()
        with ThreadPoolExecutor(max_workers=max_workers) as executor:
            # executor.map 与 pool.map 类似,但使用线程池
            results = list(executor.map(pull_data_threaded, rows_list))
        end_time = time.time()
        print(f"\n所有请求(多线程)完成,耗时: {format(end_time - start_time, '.2f')}秒")
        return results
    
    if __name__ == '__main__':
        sample_rows = [f"SN_{i}" for i in range(10)]
        print("\n开始使用 ThreadPoolExecutor 进行API请求...")
        api_results_threaded = threaded_api_requests(sample_rows, max_workers=5)
    
        for i, res in enumerate(api_results_threaded):
            if res:
                print(f"请求 {sample_rows[i]} 结果示例: {res.get('id')}")
            else:
                print(f"请求 {sample_rows[i]} 失败。")
            if i >= 2:
                break
  • 异步编程(Asynchronous Programming): 对于需要极高并发量的I/O密集型任务,asyncio配合异步HTTP客户端(如aiohttp)是最高效的解决方案。它通过事件循环(event loop)以非阻塞的方式处理I/O,避免了线程/进程切换的开销,可以同时管理成千上万个并发连接。

    import asyncio
    import aiohttp
    import time
    
    async def fetch_data_async(session, row_data):
        """
        使用aiohttp异步从Web API拉取数据。
        """
        url_api = 'https://jsonplaceholder.typicode.com/posts/1'
        try:
            post_json = {"data": row_data}
            async with session.post(url_api, json=post_json) as response:
                response.raise_for_status()
                return await response.json()
        except aiohttp.ClientError as e:
            print(f"Error fetching data for {row_data}: {e}")
            return None
    
    async def async_api_requests(rows_list):
        start_time = time.time()
        async with aiohttp.ClientSession() as session: # 创建一个异步Session
            tasks = [fetch_data_async(session, row) for row in rows_list]
            results = await asyncio.gather(*tasks) # 并行执行所有任务
        end_time = time.time()
        print(f"\n所有请求(异步)完成,耗时: {format(end_time - start_time, '.2f')}秒")
        return results
    
    if __name__ == '__main__':
        sample_rows = [f"SN_{i}" for i in range(10)]
        print("\n开始使用 asyncio 和 aiohttp 进行API请求...")
        api_results_async = asyncio.run(async_api_requests(sample_rows))
    
        for i, res in enumerate(api_results_async):
            if res:
                print(f"请求 {sample_rows[i]} 结果示例: {res.get('id')}")
            else:
                print(f"请求 {sample_rows[i]} 失败。")
            if i >= 2:
                break

3. 性能分析与注意事项

3.1 性能剖析的重要性

在进行任何优化之前,始终建议对代码进行性能剖析(profiling)。使用Python内置的cProfile模块或第三方工具可以帮助识别代码中真正的性能瓶颈,例如:

  • 进程/线程创建耗时
  • IPC序列化/反序列化耗时
  • 网络I/O等待耗时
  • CPU计算耗时

通过数据分析,可以避免盲目优化,将精力集中在最能提升性能的区域。

3.2 外部因素考量

客户端的并发策略只是影响整体性能的一部分。以下外部因素也可能显著影响API请求的耗时:

  • API服务器响应速度: 即使客户端并发度再高,如果服务器响应慢,整体速度也快不起来。
  • 网络延迟和带宽: 客户端与API服务器之间的网络条件。
  • API限流(Rate Limiting): 许多API会限制单位时间内允许的请求数量。过高的并发请求可能导致被服务器拒绝或封禁IP。在设计并发方案时,务必考虑并实现适当的限流

热门AI工具

更多
DeepSeek

DeepSeek是一款面向对话、写作、编程和推理场景的AI大模型工具。

Laper
Laper Hot

Laper是专为编剧、导演和制片人推出的 AI 原生剧本创作工具。

立刻MV
立刻MV Hot

立刻MV是一款AI文本写作工具,AI 音乐视频(MV)创作工具。

豆包大模型

豆包大模型是一款由字节跳动推出的企业级大语言模型服务平台。

PixPix
PixPix Hot

PixPix是一款面向电商视觉生产的AI商品图生成工具。

WorkBuddy

一款AI办公效率工具,主要用于腾讯云推出的AI原生桌面智能体工作台,适合需要提升相关任务效率的用户。

墨刀AI
墨刀AI Hot

一款AI图像与设计工具,主要用于产品经理的专属智能体,适合需要提升相关任务效率的用户。

讯飞智作

讯飞智作是一款AI视频创作工具,AI文本配音工具,数字人课程、营销视频制作。

超级简历WonderCV

一款AI办公效率工具,主要用于免费求职简历模版下载制作,应届生职场人必备简历制作神器,适合需要提升相关任务效率的用户。

相关专题

更多
session失效的原因
session失效的原因

session失效的原因有会话超时、会话数量限制、会话完整性检查、服务器重启、浏览器或设备问题等等。详细介绍:1、会话超时:服务器为Session设置了一个默认的超时时间,当用户在一段时间内没有与服务器交互时,Session将自动失效;2、会话数量限制:服务器为每个用户的Session数量设置了一个限制,当用户创建的Session数量超过这个限制时,最新的会覆盖最早的等等。

580

2023.10.17

session失效解决方法
session失效解决方法

session失效通常是由于 session 的生存时间过期或者服务器关闭导致的。其解决办法:1、延长session的生存时间;2、使用持久化存储;3、使用cookie;4、异步更新session;5、使用会话管理中间件。

876

2023.10.18

cookie与session的区别
cookie与session的区别

本专题整合了cookie与session的区别和使用方法等相关内容,阅读专题下面的文章了解更详细的内容。

1866

2025.08.19

treenode的用法
treenode的用法

​在计算机编程领域,TreeNode是一种常见的数据结构,通常用于构建树形结构。在不同的编程语言中,TreeNode可能有不同的实现方式和用法,通常用于表示树的节点信息。更多关于treenode相关问题详情请看本专题下面的文章。php中文网欢迎大家前来学习。

2381

2023.12.01

C++ 高效算法与数据结构
C++ 高效算法与数据结构

本专题讲解 C++ 中常用算法与数据结构的实现与优化,涵盖排序算法(快速排序、归并排序)、查找算法、图算法、动态规划、贪心算法等,并结合实际案例分析如何选择最优算法来提高程序效率。通过深入理解数据结构(链表、树、堆、哈希表等),帮助开发者提升 在复杂应用中的算法设计与性能优化能力。

336

2025.12.22

深入理解算法:高效算法与数据结构专题
深入理解算法:高效算法与数据结构专题

本专题专注于算法与数据结构的核心概念,适合想深入理解并提升编程能力的开发者。专题内容包括常见数据结构的实现与应用,如数组、链表、栈、队列、哈希表、树、图等;以及高效的排序算法、搜索算法、动态规划等经典算法。通过详细的讲解与复杂度分析,帮助开发者不仅能熟练运用这些基础知识,还能在实际编程中优化性能,提高代码的执行效率。本专题适合准备面试的开发者,也适合希望提高算法思维的编程爱好者。

377

2026.01.06

C++ 数据结构与算法实现教程合集
C++ 数据结构与算法实现教程合集

以 C++ 为实现语言,系统讲解核心数据结构与算法,涵盖链表(单链表/双链表/环检测)、栈与队列(单调栈/优先队列)、二叉树(遍历/BST/AVL/红黑树)、哈希表(开地址法/链地址法)、图(邻接表/BFS/DFS/Dijkstra/拓扑排序)、常见排序算法(快排/归并/堆排/计数排序)的实现与复杂度分析,同时分享 LeetCode 刷题技巧、竞赛编程常用模板(二分/前缀和/滑动窗口/动态规划),帮助开发者夯实算法基础。

432

2026.05.09

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

3918

2023.08.10

Kratos框架HTTP与gRPC服务开发教程
Kratos框架HTTP与gRPC服务开发教程

本专题围绕Kratos框架双协议服务开发,涵盖HTTP路由与处理器编写、参数获取、gRPC服务实现与客户端调用、metadata上下文传递、encoding编解码注册、统一响应封装、超时控制与流式响应实现方法。

0

2026.10.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Buffalo框架快速入门指南
Buffalo框架快速入门指南

共0课时 | 0人学习

Valgrind支持平台说明
Valgrind支持平台说明

共0课时 | 0人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn