Python脚本如何避免同步任务扎堆执行

wen python案例 24

本文目录导读:

Python脚本如何避免同步任务扎堆执行

  1. 使用 asyncio.Semaphore(异步场景)
  2. 使用 concurrent.futures.ThreadPoolExecutor(多线程场景)
  3. 使用队列 + 消费者模式(生产消费模型)
  4. 使用第三方库 ratelimitthrottle
  5. 分布式场景:使用 Redis 实现分布式锁或队列
  6. 最佳实践建议

在Python中避免同步任务扎堆执行,核心思路是控制并发数防止任务堆积,以下是几种常见场景的解决方案:

使用 asyncio.Semaphore(异步场景)

当使用 asyncio 时,信号量可以限制同时执行的任务数:

import asyncio
import aiohttp
async def worker(sem, url):
    async with sem:  # 获取信号量,如果已满则等待
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as resp:
                data = await resp.text()
                print(f"处理 {url}: 长度 {len(data)}")
        await asyncio.sleep(0.1)  # 模拟其他耗时操作
async def main():
    sem = asyncio.Semaphore(5)  # 最多5个任务同时执行
    urls = [f"https://httpbin.org/get?id={i}" for i in range(100)]
    # 创建所有任务但会通过信号量控制并发
    tasks = [worker(sem, url) for url in urls]
    await asyncio.gather(*tasks)
asyncio.run(main())

使用 concurrent.futures.ThreadPoolExecutor(多线程场景)

限制线程池大小来避免扎堆:

from concurrent.futures import ThreadPoolExecutor, as_completed
import time
import random
def task(item):
    """模拟一个耗时的同步任务"""
    delay = random.uniform(0.1, 0.5)
    time.sleep(delay)
    return f"任务 {item} 完成, 耗时 {delay:.2f}s"
# 控制并发数:最多3个线程同时执行
with ThreadPoolExecutor(max_workers=3) as executor:
    # 提交任务,不会立即阻塞
    futures = {executor.submit(task, i): i for i in range(20)}
    # 按完成顺序获取结果
    for future in as_completed(futures):
        item = futures[future]
        try:
            result = future.result()
            print(result)
        except Exception as e:
            print(f"任务 {item} 失败: {e}")

使用队列 + 消费者模式(生产消费模型)

适用于任务生成速度可能超过处理速度的场景:

from queue import Queue
from threading import Thread
import time
import random
class TaskConsumer:
    def __init__(self, num_workers=3, max_queue_size=20):
        self.queue = Queue(maxsize=max_queue_size)  # 限制队列大小
        self.num_workers = num_workers
        self.workers = []
    def start(self):
        for i in range(self.num_workers):
            t = Thread(target=self._worker, args=(i,), daemon=True)
            t.start()
            self.workers.append(t)
    def _worker(self, worker_id):
        while True:
            try:
                task = self.queue.get(timeout=5)  # 5秒超时退出
                print(f"Worker {worker_id}: 开始处理 {task}")
                time.sleep(random.uniform(0.2, 0.8))  # 模拟处理时间
                print(f"Worker {worker_id}: 完成 {task}")
                self.queue.task_done()
            except:
                break  # 超时退出
    def add_task(self, task):
        """添加任务,如果队列已满会阻塞"""
        self.queue.put(task)  # 如果队列满,这里会等待
        print(f"添加任务 {task}, 当前队列大小: {self.queue.qsize()}")
# 使用示例
consumer = TaskConsumer(num_workers=3, max_queue_size=10)
consumer.start()
# 模拟快速生成任务
for i in range(100):
    consumer.add_task(f"任务_{i}")
    time.sleep(0.05)  # 控制生成速度
print("所有任务已提交")
consumer.queue.join()  # 等待所有任务完成

使用第三方库 ratelimitthrottle

控制执行频率:

from ratelimit import limits, sleep_and_retry
import time
@sleep_and_retry
@limits(calls=5, period=1)  # 每秒最多5次调用
def limited_function(item):
    print(f"执行: {item}")
    time.sleep(0.3)
# 即使快速循环,也会被限速
for i in range(50):
    limited_function(i)

分布式场景:使用 Redis 实现分布式锁或队列

import redis
import time
import random
r = redis.Redis(host='localhost', port=6379, db=0)
def process_with_redis_queue(task_id):
    """从Redis列表取任务,最多同时处理5个"""
    # 如果活跃任务超过限制,等待
    while r.llen(f"active_tasks:worker_{task_id}") >= 5:
        print("等待中...")
        time.sleep(0.5)
    # 模拟处理
    print(f"处理任务 {task_id}")
    r.lpush(f"active_tasks:worker_{task_id}", task_id)
    time.sleep(random.uniform(0.5, 2))
    r.lrem(f"active_tasks:worker_{task_id}", 0, task_id)
    print(f"完成任务 {task_id}")
# 多个worker可以独立运行
for i in range(20):
    process_with_redis_queue("worker_1")

最佳实践建议

  1. 明确瓶颈类型

    • CPU密集 → 使用 ProcessPoolExecutor
    • IO密集 → 使用 ThreadPoolExecutorasyncio
    • 外部服务限制 → 使用信号量 + 限速
  2. 监控任务堆积

    import logging
    from queue import Queue
    class MonitoredQueue(Queue):
        def put(self, item, block=True, timeout=None):
            if self.qsize() > self.maxsize * 0.8:
                logging.warning(f"队列接近满: {self.qsize()}/{self.maxsize}")
            super().put(item, block, timeout)
  3. 动态调整并发数

    class AdaptiveExecutor:
        def __init__(self, min_workers=2, max_workers=10):
            self.min_workers = min_workers
            self.max_workers = max_workers
            self.current_workers = min_workers
        def adjust_workers(self, queue_size, processing_time):
            if queue_size > 50 and processing_time < 0.1:
                self.current_workers = min(self.max_workers, self.current_workers + 1)
            elif queue_size < 10 and self.current_workers > self.min_workers:
                self.current_workers -= 1

选择哪种方案取决于你的具体场景:

  • 异步IO操作 → asyncio.Semaphore
  • 同步阻塞操作 → ThreadPoolExecutor 或队列
  • 需要跨进程/跨机器 → Redis队列或消息队列(如 RabbitMQ)
  • 严格控制调用频率 → ratelimit

需要根据实际业务特点和性能要求来选择或组合使用这些策略。

抱歉,评论功能暂时关闭!