如何在 Python 多进程环境中安全高效地使用 PyMongo

千墨同学_5273

千墨同学_5273

2026-08-06

947人浏览

原创

如何在 Python 多进程环境中安全高效地使用 PyMongo

pymongo 在多进程场景下频繁创建客户端会导致 windows 系统报 winerror 10048(端口耗尽),根本原因是每个子进程内反复新建 mongoclient 实例,引发大量短生命周期 tcp 连接堆积;正确做法是为每个工作进程复用单一客户端实例。

pymongo 在多进程场景下频繁创建客户端会导致 windows 系统报 winerror 10048(端口耗尽),根本原因是每个子进程内反复新建 mongoclient 实例,引发大量短生命周期 tcp 连接堆积;正确做法是为每个工作进程复用单一客户端实例。

在使用 multiprocessing 与 PyMongo 协同处理高并发数据库操作(如批量查询、更新)时,一个常见但隐蔽的陷阱是:看似“每个进程独立创建 MongoClient”,实则因调用频次过高导致底层 socket 资源快速耗尽。您遇到的 WinError 10048(“Only one usage of each socket address is normally permitted”)正是 Windows 系统对本地端口重用限制触发的典型错误——并非 MongoDB 服务异常,而是客户端连接管理失当。

问题核心在于:虽然 process_ticker_attention 中创建了 mongo_p,但其内部调用的 get_all_post_dates() 和 query_records_by_date() 各自又新建了独立的 MongoClient 实例(见原代码中两处 pymongo.MongoClient(...))。假设单个 ticker 对应 2000 个日期,则每进程将创建至少 4000+ 次客户端连接(2 函数 × 2000 次循环),而 MongoClient 默认启用连接池且连接不会立即释放,极易突破 Windows 的临时端口范围(默认约 16384 个可用端口),尤其在 6 个进程并行时。

✅ 正确解决方案:利用 multiprocessing.Pool 的 initializer 机制,在每个子进程启动时初始化一次全局 MongoClient,并在整个进程生命周期内复用它。这既避免了重复建连开销,又彻底规避端口冲突。

python-parallelization
python-parallelization

将顺序 Python 代码转换为并行/并发实现,用于在需要并行化 Python 代码、提升性能时使用。

下载

以下是优化后的关键代码结构:

import multiprocessing
import pymongo
import sys

# 全局变量(仅在子进程中有效)
mongo_client = None

def init_worker():
    """子进程初始化函数:创建并缓存 MongoClient"""
    global mongo_client
    # 注意:connectTimeoutMS 和 socketTimeoutMS 应合理设置,避免阻塞
    mongo_client = pymongo.MongoClient(
        "mongodb://localhost:27017/",
        connectTimeoutMS=30000,
        socketTimeoutMS=30000,
        maxPoolSize=10,      # 控制每个进程最大连接数,防止资源过载
        minPoolSize=1,       # 保持最小连接,加速后续请求
        serverSelectionTimeoutMS=5000
    )

def get_all_post_dates(ticker):
    """复用全局 mongo_client,不再新建实例"""
    global mongo_client
    # 假设数据库名为 'StockForum'
    db = mongo_client['StockForum']
    # 示例聚合逻辑(请按实际需求替换)
    pipeline = [
        {"$match": {"ticker": ticker}},
        {"$group": {"_id": "$date"}},
        {"$project": {"date": "$_id", "_id": 0}}
    ]
    return [doc["date"] for doc in db[ticker].aggregate(pipeline)]

def query_records_by_date(ticker, start_of_day):
    """同样复用全局 mongo_client"""
    global mongo_client
    db = mongo_client['StockForum']
    return list(db[ticker].find({"date": start_of_day}))

def process_ticker_attention(ticker, finish_num, lock):
    try:
        ticker_dates = get_all_post_dates(ticker)
        for date in ticker_dates:
            data = query_records_by_date(ticker, date)
            # 使用同一 client 更新目标集合
            mongo_client['stock']['attention'].update_one(
                {'date': date},
                {'$set': {f'post_numbers.{ticker}': len(data)}}
            )

        with lock:
            finish_num.value += 1
            sys.stdout.write(f'\rfinish_count: {finish_num.value}')
            sys.stdout.flush()
    except Exception as e:
        print(f'error in {ticker}: {e.__class__.__name__} - {e}')

if __name__ == '__main__':
    # 主进程仅用于分发任务,不执行 DB 操作
    database_name = 'StockForum'

    # 使用 Manager 创建共享状态
    with multiprocessing.Manager() as manager:
        lock = manager.Lock()
        finish_num = manager.Value('i', 0)

        # 创建进程池,指定 initializer
        with multiprocessing.Pool(
            processes=6,
            initializer=init_worker
        ) as pool:
            # 获取 ticker 列表(主进程执行,避免跨进程传递大型对象)
            mongo_main = pymongo.MongoClient("mongodb://localhost:27017/")
            tickers = mongo_main[database_name].list_collection_names()

            # 异步提交任务(注意:无需再传 mongo_client)
            results = [
                pool.apply_async(process_ticker_attention, (t, finish_num, lock))
                for t in tickers
            ]
            # 等待全部完成
            for r in results:
                r.get()  # 可选:捕获子进程异常

? 关键注意事项:

  • ❌ 禁止在任何被 apply_async 调用的函数内部新建 MongoClient —— 所有数据库操作必须通过 init_worker 初始化的全局 mongo_client 完成;
  • ✅ maxPoolSize 是核心调优参数:设为 10 表示该进程最多维持 10 条活跃连接,足以应对并发读写,又不会过度占用端口;
  • ⚠️ MongoClient 实例不可跨进程传递(会引发 Pickle 错误),必须在子进程内初始化;
  • ? 如需更细粒度控制(如不同任务用不同数据库),可将 init_worker 改为接收参数(如 init_worker(db_uri, db_name)),配合 functools.partial 使用;
  • ? Linux/macOS 用户虽较少遇 WinError 10048,但仍建议采用此模式——它显著提升性能并增强稳定性。

遵循这一模式后,您的多进程 PyMongo 应用将稳定运行于数千级文档处理场景,彻底告别端口耗尽错误。

Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!

相关专题

更多
python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

2023.07.20

1751

4

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

2023.07.25

4524

7

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

2023.07.31

1729

3

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

2023.08.03

26657

23

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3227

5

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.04

3247

5

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

1183

5

python合并两个列表
python合并两个列表

Python是一种强大的编程语言,具有许多方便的功能和工具。在Python中,有多种方法可以合并两个列表。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

2023.08.10

596

4

python是前端还是后端
python是前端还是后端

Python属于前端也属于后端,其灵活性和丰富的生态系统使得开发人员能够在不同的领域中灵活运用。本专题为大家提供python相关的文章、下载、课程内容,供大家免费下载体验。

2023.08.11

2523

5

热门下载

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

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Uvicorn官方安装与入门手册
Uvicorn官方安装与入门手册

共0课时 | 0人学习

Python uv官方项目管理指南
Python uv官方项目管理指南

共0课时 | 0人学习

Python uv官方安装与入门文档
Python uv官方安装与入门文档

共0课时 | 0人学习