多线程并发

若需并发,请在应用内用队列/协程控制

多并发解决方案

给你两种简单好用的并发控制方案,避免开多 worker 占满显存、又能保证 SSE 正常流式输出。

方案 A(最快落地):信号量限流(限并发 N=1 或小于显卡承受上限)

  • 特点:无需大改,直接在现有流式生成器外层加锁即可。
  • 适合:先把系统稳定跑起来。

在 survey_service.py 顶部加:

python

import threading

GPU_CONCURRENCY = 1 # 根据显存调小,比如 1

gpu_sem = threading.BoundedSemaphore(GPU_CONCURRENCY)

在流式生成处包一层(以 outline_generation_api 为例,full_survey_generation_api 同理):

python

def outline_generation_api():

... 解析入参省略

def stream():

yield json.dumps({"stage": 0, "stageDsc": "queued"}) + "\n"

with gpu_sem: # 限制同时仅有 N 个任务占用模型/GPU

for chunk in generate_outline(

session_id, topic, model, section_num, outline_reference_num,

api_url, api_key, saving_path, req_discribe, language

):

yield chunk

return Response(stream_with_context(stream()), mimetype='text/event-stream')

启动时保持单进程:

bash

nohup uvicorn survey_service:asgi_app --host 0.0.0.0 --port 15001 --workers 1 --log-level=info >> logs/span_data_monitor.log 2>&1 &

方案 B(更平滑):应用内任务队列 + 后台工作线程(公平排队,前端可看到"排队中")

  • 特点:请求快速返回流,真正的重活在后台工作线程里跑;可以控制并发为 1 或 2。
  • 适合:需要"排队位次/保持连接"的场景。

在 survey_service.py 顶部加:

python

import threading, queue, time, json

GPU_CONCURRENCY = 1

gpu_sem = threading.BoundedSemaphore(GPU_CONCURRENCY)

task_queue = queue.Queue()

def worker_loop():

while True:

job = task_queue.get() # {"runner": callable->iterator, "out": Queue, "done": Event}

try:

with gpu_sem:

for chunk in job["runner"]():

job["out"].put(chunk)

except Exception as e:

job["out"].put(json.dumps({"stage": -1, "stageDsc": f"error: {e}"}) + "\n")

finally:

job["done"].set()

task_queue.task_done()

启动 N 个后台线程=N 即并发数

for _ in range(GPU_CONCURRENCY):

threading.Thread(target=worker_loop, daemon=True).start()

在接口里改为入队并从"专属输出队列"读(以 outline_generation_api 为例):

python

def outline_generation_api():

... 解析入参省略

out_q = queue.Queue(maxsize=256)

done = threading.Event()

def runner():

这里就是你原来的生成器

return generate_outline(

session_id, topic, model, section_num, outline_reference_num,

api_url, api_key, saving_path, req_discribe, language

)

入队

task_queue.put({"runner": runner, "out": out_q, "done": done})

position = task_queue.qsize() # 粗略队列位置(放入后得到的是当前长度)

def stream():

提示已排队

yield json.dumps({"stage": 0, "stageDsc": f"queued, pos≈{position}"}) + "\n"

持续把后台线程产出的分片写给客户端

while not done.is_set() or not out_q.empty():

try:

chunk = out_q.get(timeout=1)

yield chunk

except queue.Empty:

yield ":\n\n" # SSE keepalive

return Response(stream_with_context(stream()), mimetype='text/event-stream')

使用要点

  • 仍然用单 worker 启动 uvicorn;并发控制交给应用内的信号量或队列。
  • GPU_CONCURRENCY 根据显存设置(通常 1);需要提速时先评估模型占显存峰值再调大。
  • 前端如需要"队列位置/预计等待"可在 stage:0 里携带 pos≈X,并定时轮询队列长度优化显示。
  • 若任务很长,建议在生成器关键步骤(如每段生成、引用处理、docx 转换)追加阶段日志,方便观察进度。

两种方案可先用 A 快速稳定,再升级到 B 获得更好的排队体验。