使用Motor+Asyncio写入Mongo时多并发出现无法创建新线程错误
问题描述
使用asyncio结合motor库向MongoDB写入数据,少量bulk_writes操作运行正常,但并发大量写入时抛出RuntimeError: can't start new thread异常。
报错栈
File "/usr/local/lib/python3.7/site-packages/motor/metaprogramming.py", line 77, in method **unwrapped_kwargs) File "/usr/local/lib/python3.7/site-packages/motor/frameworks/asyncio/__init__.py", line 74 in run_on_executor _EXECUTOR, functools.partial(fn, *args, **kwargs)) File "uvloop/loop.pyx", line 2702, in uvloop.loop.Loop.run_in_exector File "/usr/local/lib/python3.7/concurrent/features/thread.py", line 160, in submit self._adjust_thread_count() File "/usr/local/lib/python3.7/concurrent/features/thread.py", line 181, in _adjust_thread_count t.start() File "usr/local/lib/python3.7/threading.py", line 847, in start _start_new_thread(self._bootsrap, ()) RuntimeError: can't start new thread
关键信息
- 本地运行无错误,Openshift环境出现问题;
- 在Openshift中通过gunicorn以
gunicorn app:app --worker-class uvicorn.workers.UvicornWorker启动; - Openshift中仅1个worker时正常,2个及以上worker时出现问题;
- 仅同时开启2个AsyncIOMotorClient连接;
- 使用pymongo(无asyncio支持)运行几乎相同代码无错误;
- 移除Mongo相关代码后运行正常。
解决方案
1. 调整Motor全局线程池大小
Motor的asyncio实现依赖全局ThreadPoolExecutor执行同步PyMongo操作,默认线程池大小为min(32, os.cpu_count() + 4)。多worker场景下,每个worker进程都会创建独立线程池,叠加后容易触达Openshift容器的线程配额上限。
在应用启动时手动设置更小的线程池:
import concurrent.futures from motor.frameworks.asyncio import _EXECUTOR # 在创建AsyncIOMotorClient前执行 _EXECUTOR = concurrent.futures.ThreadPoolExecutor(max_workers=8)
根据容器资源和并发量调整max_workers,比如从默认32降至8或更低,确保多worker下总线程数不超限制。
2. 复用Motor客户端实例
每个worker进程只初始化一个AsyncIOMotorClient实例,避免在请求处理函数内重复创建客户端。将客户端初始化放在应用启动的全局代码中,而非请求上下文内。
3. 优化Gunicorn worker配置
UvicornWorker是异步worker,无需过多进程数。结合Openshift容器CPU核心数调整worker数量,比如设置--workers=1或等于CPU核心数,减少进程间的线程资源竞争。
4. 检查Openshift容器线程配额
在容器内执行ulimit -u查看最大进程/线程数。如果配额较低,可申请调整容器资源配额,或修改securityContext中的线程相关配置。
5. 批量写入优化
合并多个小批量写入请求,降低单次并发提交到线程池的任务数。比如将多个小bulk合并为一个较大的批量,减少线程创建频率。
内容的提问来源于stack exchange,提问作者Ofir
相关产品推荐
相关产品推荐

