For AI agents: the complete documentation index is available at /tc39-atlas/llms.txt, the full documentation bundle is available at /tc39-atlas/llms-full.txt, and this page is available as Markdown at /tc39-atlas/proposals/year/pending/proposal-concurrency-control.md.
  • 简体中文
  • Concurrency Control S1

    中文标题:并发控制

    提案概览
    提案速览

    该提案解决了 JavaScript 缺少一种通用机制来表达所需并发量并对其进行协调的问题。它定义了用于获取和释放受限资源访问权的 Governor 和 AbortableGovernor 接口,并提供 CountingGovernor 作为计数信号量实现。它还为异步迭代器辅助方法添加了可选的并发参数,并引入了用于并发受限异步迭代的 limit 方法。

    Note

    以下 README 来自上游仓库,其中的阶段或状态标注可能滞后;当前信息以提案概览为准。

    JavaScript 并发控制提案

    阶段: 1

    推动者: Michael Ficarra, Luca Casonato

    作者: Michael Ficarra, Luca Casonato, Kevin Gibbons

    本提案旨在提供一种描述所需并发量的机制,以及一种实现该并发量的协调机制。这可以用于限制对共享资源的并发访问,或者为原本无界的工作负载设定目标并发量。

    本提案的一个主要动机是 async iterator helpers 提案 中的并发支持。虽然该提案竭尽全力允许对其生成的异步迭代器进行并发迭代(例如通过 mapfilter),但它没有提供任何并发消费异步迭代器的方式(例如通过 someforEach)。此外,该提案也没有提供通用地限制异步迭代器并发迭代的机制。本提案试图解决这些被推迟的需求。

    这里提出的并发控制机制也受到异步迭代之外的许多其他用例的推动。例如,应用程序可能希望限制某个函数的调用并发度,或限制并发的文件读取、写入或网络请求。本提案旨在提供一种通用的 JavaScript 并发控制机制,可用于各种不同的用例。

    向委员会进行的演示

    提案

    本提案包含 3 个主要组成部分:Governor 和 AbortableGovernor 接口、CountingGovernor 类,以及 AsyncIterator.prototype 集成。

    Governor

    Governor 接口用于获得对受限资源的访问,并在之后发出你已用完该资源的信号。它有意被设计为允许动态更改限制。

    Governor 接口只要求一个方法:acquire,返回一个 Promise,最终解析为一个 GovernorTokenGovernorToken 有一个 release 方法,用于表示不再需要该资源。GovernorToken 还可以使用 Explicit Resource Management 提案 中的 using 语法自动释放。

    某些资源的获取是可中止的。对于这些情况,提供了 AbortableGovernor 接口。该接口除了 Governor 接口的 acquire 方法外,还有一个 acquireAbortable 方法。acquireAbortable 同时返回一个中止回调和一个用于 GovernorToken 的 Promise。调用 abort 回调会请求 AbortableGovernor 中止获取并拒绝用于 GovernorToken 的 Promise。调用 abort 可能不产生任何作用,具体取决于受管资源是否已被获取(即使调用者尚不可见)。

    Governor 旨在控制互相信任各方之间对资源的访问。对于对抗性场景,应改用 Capability

    Governor 名称取自机动车中的限速装置

    另外还有一个带辅助方法的 Governor 和 AbortableGovernor 构造函数。

    AbortableGovernor 扩展 Governor。

    new.target 是 Governor 构造函数本身时,它无条件抛出。为了使辅助方法可用,可以按如下方式实现一个具体的 Governor:

    const someGovernor = {
      __proto__: Governor.prototype,
      acquire() {
        // ...
      },
    };

    with(fn: () => R): Promise<R> 辅助方法接受一个函数,并自动获取/释放一个 GovernorToken。一个近似实现:

    Governor.prototype.with = async (fn) => {
      using void = await this.acquire();
      return await fn();
    };

    wrap(fn: (...args) => R): (...args) => Promise<R> 辅助方法接受一个函数,并返回一个行为相同但受此 Governor 限制并发度的函数。一个近似实现:

    Governor.prototype.wrap = fn => {
      const governor = this;
      return async function() {
        using void = await governor.acquire();
        return await fn.apply(this, arguments);
      };
    };

    类似地,wrapIterator(it: Iterator<T> | AsyncIterator<T>): AsyncIterator<T> 接受一个 Iterator 或 AsyncIterator,并返回一个产生相同值但受此 Governor 限制并发度的 AsyncIterator。

    开放问题
    • acquire 协议是否应该基于 Symbol?
    • 是否需要一个同步/会抛出的 acquire?
      • tryAcquire(): GovernorToken
      • 或者也许不抛出?tryAcquire(): GovernorToken | null
    • 非抛出式 Governor() 构造函数
      • 接受一个 acquire: () => Promise<GovernorToken> 函数
      • 是否也接受一个 tryAcquire 函数?
      • 没有它也很容易实现
    • 替代名称:Regulator?
    • 同时实现 "release" 和 Symbol.dispose 有点麻烦

    CountingGovernor

    本提案涵盖了 Luca 的 Semaphore 提案

    CountingGovernor 是一个实现 Governor 接口并扩展 Governor 的计数信号量。它可以被赋予一个非负整数 Number capacity,并负责确保同时活动的 GovernorToken 不超过该数量。

    开放问题
    • 空闲监听器有用吗?
      • 每当 CountingGovernor 达到 "full" 容量(0 个活动 GovernorToken)时触发
      • addIdleListener(cb: () => void): void
      • removeIdleListener(cb: () => void): void
      • 使用回调接口还是 EventTarget?
    • 跨 Agent 共享 CountingGovernor 是否有顾虑?
    • 替代名称:CountingGovernor?CountingRegulator?

    AsyncIterator.prototype 集成

    本提案为以下异步迭代器辅助方法添加了一个可选的并发参数:

    • .toArray([ governor ])
    • .forEach(fn [, governor ])
    • .some(predicate [, governor ])
    • .every(predicate [, governor ])
    • .find(predicate [, governor ])
    • .reduce(reducer [, initialValue [, governor ]])

    当未传入时,这些方法按串行方式运行,与 async iterator helpers 提案中的行为一致。

    本提案还新增了一个 limit(governor) 方法(它是 governor.wrapIterator(iterator) 的对偶),它返回一个并发受限的 AsyncIterator。

    由于 CountingGovernor 将是一种极其常用的 Governor,在任何 AsyncIterator.prototype 方法接受 Governor 的地方,都可以改为传入一个非负整数 Number。它会被视为传入了具有该容量的 CountingGovernor。因此,我们能够拓宽 buffered 辅助方法的第一个参数,使其除了接受 async iterator helpers 提案中的非负整数 Number 之外,也接受一个 Governor。

    开放问题
    • reduce 的参数顺序:很糟糕?
    • buffered 的参数顺序

    提案摘要

    • Governor
      • Governor 接口
        • acquire(): Promise<GovernorToken>
      • Governor() 构造函数
        • 直接构造时抛出
      • Governor.prototype
        • with(fn: () => R): Promise<R>
        • wrap(fn: (...args) => R): (...args) => Promise<R>
        • wrapIterator(it: Iterator<T> | AsyncIterator<T>): AsyncIterator<T>
      • GovernorToken.prototype
        • release(): void === [Symbol.dispose](): void
    • CountingGovernor
      • CountingGovernor(capacity: number) 构造函数
      • 扩展 Governor
      • 实现 Governor 接口
      • 可跨线程共享
    • AsyncIterator.prototype
      • buffered(limit: Governor | integer, prepopulate = false)
      • limit(limit: Governor | integer)
      • 为所有消费型方法添加并发参数(Governor | integer

    常见问题

    为什么使用通用的 Governor 接口?

    虽然计数信号量是一种常见的并发控制机制,但控制并发的方式还有很多。例如:

    • 一个在执行限制前允许突发活动的 governor(例如一个允许 10 个并发操作,但在前 5 秒允许 20 个并发操作的信号量)。
    • 一个使用分布式锁或共识算法在多个机器之间强制执行并发限制的分布式 governor。
    • 一个基于系统当前负载强制执行并发限制的 governor(例如一个允许 10 个并发操作,但如果系统负载低于 50% 则允许 20 个并发操作的信号量)。

    由于用例如此多样,重要的是让开发者能够灵活地在内置 JavaScript API 中使用自己的并发控制机制。

    CountingGovernor 类提供了一种简单而常见的并发控制机制,可在许多情况下使用。预计许多开发者会使用 CountingGovernor 来满足其并发控制需求。然而,由于 API 并不显式接受 CountingGovernor 实例,而是接受任何实现 Governor 接口的对象,开发者可以在需要时使用自己的并发控制机制。