繁体   English   中英

在 Uvicorn 中使用具有多个工作人员的多处理(线程锁)

[英]Using Multiprocessing in Uvicorn with multiple workers (thread lock)

我正在构建一个 API,它通过 uvicorn 提供 FastAPI。 API 具有使用 python 多处理库的端点。

端点为 CPU 绑定任务生成多个进程以并行执行它们。 这是高级代码逻辑概述:

import multiprocessing as mp

class Compute:
    
    def single_compute(self, single_comp_data):
        # Computational Task CPU BOUND
        global queue
        queue.put(self.compute(single_comp_data))

    def multi_compute(self, task_ids):
        # Prepare for Compuation
        output = {}
        processes = []
        global queue
        queue = mp.Queue()
        
        # Start Test Objs Computation
        for tid in task_ids:
            # Load  task data here, to make use of object in memory cache
            single_comp_data = self.load_data_from_cache(tid)
            p = mp.Process(target=self.single_compute, args=single_comp_data)
            p.start()
            processes.append(p)

        # Collect Parallel Computation
        for p in processes:
            result = queue.get()
            output[result["tid"]]= result
            p.join()

        return output

这是简单的 API 代码:

from fastapi import FastAPI, Response
import json


app = FastAPI()
#comp holds an in memory cache, thats why its created in global scope
comp = Compute()

@app.get("/compute")
def compute(task_ids):
    result = comp.multi_compute(task_ids)
    return Response(content=json.dumps(result, default=str), media_type="application/json")

当像这样与多个工人一起运行时:

uvicorn compute_api:app --host 0.0.0.0 --port 7000 --workers 2

我收到此 python 错误

TypeError: can't pickle _thread.lock objects

只有 1 个工作进程就可以了。 该程序在 UNIX/LINUX 操作系统上运行。

有人可以向我解释为什么这里有多个 uvicorn 进程无法分叉一个新进程,以及为什么我会遇到这个胎面锁?

最后应该实现的目标很简单:

使用该 uvicorn 进程的 memory 副本生成多个其他进程(通过 fork 的子进程)的 uvicorn 进程。 执行 cpu 绑定任务。

TypeError:无法腌制 _thread.lock 对象

源于您传递到子流程中的任何数据

p = mp.Process(target=self.single_compute, args=single_comp_data)

包含不可腌制的 object。

所有发送到multiprocessing子进程的 args/kwargs(无论是通过 Process,还是Pool中的高级方法)都必须是可腌制的,同样,function 运行的返回值必须是可腌制的,以便可以将其发送回父进程.

如果您在 UNIX 上并使用fork start 方法进行多处理(这是 Linux 上的默认设置,但不是在 macOS 上),您还可以利用写时复制 ZCD69B4957F06CD69B4957F06CD69B4957F06CD0818D7BFE3D61 语义来避免“复制”“复制”子进程通过使数据可用,例如通过实例 state,一个全局变量,...,在生成子进程之前,并让它通过引用获取它,而不是将数据本身作为参数传递下来。

此示例使用imap_unordered来提高性能(假设不需要按顺序处理 id),并将返回一个 dict 将输入 ID 映射到它创建的结果。

class Compute:
    _cache = {}  # could be an instance variable too but whatever

    def get_data(self, id):
        if id not in self._cache:
            self._cache[id] = get_data_from_somewhere(id)
        return self._cache[id]

    def compute_item(self, id):
        data = self.get_data(id)
        result = 42  # ... do heavy computation here ...
        return (id, result)

    def compute_result(self, ids) -> dict:
        for id in ids:
             self.get_data(id)  # populate in parent process
        with multiprocessing.Pool() as p:
             return dict(p.imap_unordered(self.compute_item, ids))

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM