← 中文资源

简体中文版本

异步信号量并发限制

用基于 promise 的信号量限制异步任务并发,排队等待者并安全释放许可。

阅读英文原文

功能说明

该示例把并发上限显式化。acquire 在有空闲许可时立即返回一个释放函数,否则返回一个 promise 直到有许可被释放。run 封装了获取、执行与释放,并用 finally 保证即使任务抛错也会释放,避免许可泄漏导致后续任务永久等待。

关键代码

等待者存放在队列里,release 时先递减活跃计数,再唤醒队首等待者。唤醒时直接把它计入活跃并把释放函数交给它,保证许可在交接期间不被重复使用。release 在无活跃许可时抛错,暴露重复释放或缺少 acquire 的编程错误。等待者以先进先出被唤醒,但没有严格公平保证,重负载下顺序可能受调度影响。

使用注意

许可上限应设为被保护资源的真实容量,如数据库连接数或下游配额,设得过高会让限制形同虚设。该信号量只限制并发,不提供超时;在资源长期饱和时应为 acquire 增加超时或取消,否则调用方可能无限等待。它适用于进程内协调,跨进程需借助集中式限流或队列。务必通过 run 或 finally 释放,任何漏释放都会永久削减可用许可。

代码示例

export class Semaphore {
  constructor(limit) {
    if (!Number.isInteger(limit) || limit <= 0) throw new RangeError('limit must be a positive integer');
    this.limit = limit;
    this.active = 0;
    this.waiters = [];
  }

  async acquire() {
    if (this.active < this.limit) {
      this.active += 1;
      return () => this.release();
    }
    await new Promise((resolve) => this.waiters.push(resolve));
    this.active += 1;
    return () => this.release();
  }

  release() {
    if (this.active === 0) throw new Error('release without acquire');
    this.active -= 1;
    const next = this.waiters.shift();
    if (next) {
      this.active += 1;
      next(() => this.release());
    }
  }

  async run(task) {
    const release = await this.acquire();
    try {
      return await task();
    } finally {
      release();
    }
  }
}