Lean 语言参考手册

21.11. 任务与线程🔗

任务是编写多线程代码的基本原语。 Task α 表示一个会在某一时刻兑现为 α 类型值的计算;该计算可以在另一个线程上执行。 任务兑现后即可读取其值;若在兑现前尝试取得其值,当前线程会阻塞,直至任务兑现。 任务类似于 JavaScript 中的承诺、Rust 中的 JoinHandle 以及 Scala 中的 Future。

任务既可以执行纯计算,也可以执行 IO 动作。 纯任务的 API 类似于悬式的 API:Task.spawn 从 Unit → α 函数创建 Task α,而 Task.get 等待函数值计算完毕后将其返回。 该值会被缓存,后续请求无须重新计算。 关键区别在于计算发生的时机:悬式的值只有在被强制求值时才计算,而任务会伺机在另一线程中执行。

IO 中的任务使用 IO.asTask 创建。 类似地,BaseIO.asTask 与 EIO.asTask 用于在其他 IO 单子中创建任务。 这些任务可能产生副作用,也可以与其他任务通信。

当任务的最后一个引用被丢弃时,该任务会被取消。 使用 Task.spawn 创建的纯任务会在取消时终止。 使用 IO.asTask、EIO.asTask 或 BaseIO.asTask 生成的任务会继续执行,必须使用 IO.checkCanceled 显式检查是否已取消。 可以使用 IO.cancel 显式取消任务。

Lean 运行时维护一个用于运行任务的线程池。 若设置了环境变量 LEAN_NUM_THREADS,线程池大小由它决定;否则由当前机器的逻辑处理器数量决定。 线程池大小并非硬性上限;在某些情况下,为避免死锁可以超出该大小。 默认情况下,这些线程用于运行任务;每个任务都有一个优先级(Task.Priority),高优先级任务先于低优先级任务执行。 也可以用足够高的优先级生成任务,从而为其分配专用线程。

🔗type
Task.{u} (α : Type u) : Type u
Task.{u} (α : Type u) : Type u

Task α 是异步计算的原语。 它表示一个最终会解析为 α 类型值的计算,该计算可能在另一线程上进行。这类似于 Scala 中的 Future、Javascript 中的 Promise 和 Rust 中的 JoinHandle。

任务在运行时中使用覆写的表示。

21.11.1. 创建任务🔗

纯任务通常应使用 Task.spawn 创建;Task.pure 则表示一个已经兑现为所给值的任务。 非纯任务由某个 asTask 动作创建。

21.11.1.1. 纯任务🔗

纯任务可以在 IO 单子族之外创建。 当其最后一个引用被丢弃时,它们会终止。

🔗定义
Task.spawn.{u} {α : Type u} (fn : Unit → α) (prio : Task.Priority := Task.Priority.default) : Task α
Task.spawn.{u} {α : Type u} (fn : Unit → α) (prio : Task.Priority := Task.Priority.default) : Task α

spawn fn : Task α 构造并立即启动一个新任务,以异步求值函数 fn () : α。

如果提供了 prio,它就是该任务的优先级。

🔗Task 的构造子
Task.pure.{u} {α : Type u} (get : α) : Task α
Task.pure.{u} {α : Type u} (get : α) : Task α

Task.pure (a : α) 构造一个已经解析为值 a 的任务。

21.11.1.2. 非纯任务🔗

使用某个 asTask 函数生成带副作用的任务时,务必要真正执行所得的 IO 动作。 每次执行所得动作时都会生成一个任务;调用 asTask 时并不会生成任务。 即使不再有任何引用,非纯任务仍会继续运行,不过此时会发出取消请求。 也可以使用 IO.cancel 显式请求取消。 非纯任务必须使用 IO.checkCanceled 检查取消请求。

🔗不透明定义
BaseIO.asTask {α : Type} (act : BaseIO α) (prio : Task.Priority := Task.Priority.default) : BaseIO (Task α)
BaseIO.asTask {α : Type} (act : BaseIO α) (prio : Task.Priority := Task.Priority.default) : BaseIO (Task α)

以优先级 prio 在单独的 Task 中运行 act。

运行所得的 BaseIO 操作会立即启动任务。对 Task 的纯访问不会影响非纯操作 act。

与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
EIO.asTask {ε α : Type} (act : EIO ε α) (prio : Task.Priority := Task.Priority.default) : BaseIO (Task (Except ε α))
EIO.asTask {ε α : Type} (act : EIO ε α) (prio : Task.Priority := Task.Priority.default) : BaseIO (Task (Except ε α))

以优先级 prio 在单独的 Task 中运行 act。由于 EIO ε 操作可能抛出 ε 类型的异常,任务结果是 Except ε α。

运行所得的 IO 操作会立即启动任务。对 Task 的纯访问不会影响非纯操作 act。

与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
IO.asTask {α : Type} (act : IO α) (prio : Task.Priority := Task.Priority.default) : BaseIO (Task (Except IO.Error α))
IO.asTask {α : Type} (act : IO α) (prio : Task.Priority := Task.Priority.default) : BaseIO (Task (Except IO.Error α))

以优先级 prio 在单独的 Task 中运行 act。由于 IO 操作可能抛出 IO.Error 类型的异常,任务结果是 Except IO.Error α。

运行所得的 BaseIO 操作会立即启动任务。对 Task 的纯访问不会影响非纯操作 act。由于 IO 操作可能抛出 IO.Error 类型的异常,任务结果是 Except IO.Error α。

与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

21.11.1.3. 优先级🔗

线程调度器使用任务优先级把任务分配给线程。 在 default 到 max 的优先级范围内,高优先级任务总是先于低优先级任务执行。 以 dedicated 优先级生成的任务会被分配各自的专用线程,不会与其他任务争用线程池中的线程。

🔗定义

任务优先级。

优先级较高的任务总是在优先级较低的任务之前调度。优先级高于 Task.Priority.max 的任务会在专用线程上调度。

🔗定义

所生成任务的默认优先级,也是最低优先级:0。

🔗定义

所生成任务的最高常规优先级:8。

以高于 Task.Priority.max 的优先级生成任务并非错误,但会为该任务生成专用工作线程。这由 Task.Priority.dedicated 表示。常规优先级任务会放入线程池,并按优先级顺序处理。

🔗定义

表示任务应在专用线程上调度。

任何高于 Task.Priority.max 的优先级都会使任务立即在专用线程上调度。这对长时间运行和/或受 I/O 限制的任务尤其有用,因为为减少上下文切换,Lean 默认分配的非专用工作线程不会超过核心数。

21.11.2. 任务结果🔗

🔗定义
Task.get.{u} {α : Type u} (self : Task α) : α
Task.get.{u} {α : Type u} (self : Task α) : α

阻塞当前线程,直到给定任务执行完毕,然后返回任务结果。如果当前线程自身正在执行一个(非专用)任务,则等待期间会临时将线程池的最大大小增加一,以确保进程不会因线程池资源耗尽而死锁。请注意,当前线程解除阻塞时,可能会暂时有超过所配置线程池大小的任务同时运行,直到足够多的任务执行完毕。

在可行的情况下,应优先使用 Task.map 和 Task.bind 来建立任务依赖,而不是使用 Task.get,因为它们不需要以这种方式临时扩展线程池。尤其是,在任务续体中以 (sync := true) 调用 Task.get 会引发恐慌,因为此时续体显然并不“廉价”,否则还可能发生死锁。应改为返回所等待的任务,并使用 Task.bind/IO.bindTask 将其解包。

🔗不透明定义
IO.wait {α : Type} (t : Task α) : BaseIO α
IO.wait {α : Type} (t : Task α) : BaseIO α

等待任务完成,然后返回其结果。

🔗不透明定义
IO.waitAny {α : Type} (tasks : List (Task α)) (h : tasks.length > 0 := by exact Nat.zero_lt_succ _) : BaseIO α
IO.waitAny {α : Type} (tasks : List (Task α)) (h : tasks.length > 0 := by exact Nat.zero_lt_succ _) : BaseIO α

等待列表中的任一任务完成,然后返回其结果。

21.11.3. 任务定序🔗

这些运算符从已有任务创建新任务。 只要可能,最好使用 Task.map 或 Task.bind,而不要在新任务中手动调用 Task.get,因为前两者不会暂时增大线程池。

🔗定义
Task.map.{u_1, u_2} {α : Type u_1} {β : Type u_2} (f : α → β) (x : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : Task β
Task.map.{u_1, u_2} {α : Type u_1} {β : Type u_2} (f : α → β) (x : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : Task β

map f x 将函数 f 映射到任务 x 上:它会构造(并立即启动)一个新任务,等待 x 的值可用,然后对结果调用 f。

如果提供了 prio,它就是该任务的优先级。 如果将 sync 设为 true,那么当 x 已经完成时,f 在当前线程上执行;否则在 x 完成所在的线程上执行。此时忽略 prio。仅当执行 f 的开销很小且不会阻塞时才应这样做。

🔗定义
Task.bind.{u_1, u_2} {α : Type u_1} {β : Type u_2} (x : Task α) (f : α → Task β) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : Task β
Task.bind.{u_1, u_2} {α : Type u_1} {β : Type u_2} (x : Task α) (f : α → Task β) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : Task β

bind x f 对任务 x 和函数 f 执行单子的“绑定”操作:它会构造(并立即启动)一个新任务,等待 x 的值可用,然后对结果调用 f,得到另一个任务,再运行该任务以取得结果。

如果提供了 prio,它就是该任务的优先级。 如果将 sync 设为 true,那么当 x 已经完成时,f 在当前线程上执行;否则在 x 完成所在的线程上执行。此时忽略 prio。仅当执行 f 的开销很小且不会阻塞时才应这样做。

🔗定义
Task.mapList.{u_1, u_2} {α : Type u_1} {β : Type u_2} (f : List α → β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : Task β
Task.mapList.{u_1, u_2} {α : Type u_1} {β : Type u_2} (f : List α → β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : Task β

创建一个任务;当 tasks 中的所有任务都完成后,该任务计算把 f 应用于这些任务结果所得的值。

🔗不透明定义
BaseIO.mapTask.{u_1} {α : Type u_1} {β : Type} (f : α → BaseIO β) (t : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task β)
BaseIO.mapTask.{u_1} {α : Type u_1} {β : Type} (f : α → BaseIO β) (t : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task β)

创建一个新任务,等待 t 完成,然后对其结果运行 BaseIO 操作 f。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
EIO.mapTask.{u_1} {α : Type u_1} {ε β : Type} (f : α → EIO ε β) (t : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except ε β))
EIO.mapTask.{u_1} {α : Type u_1} {ε β : Type} (f : α → EIO ε β) (t : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except ε β))

创建一个新任务,等待 t 完成,然后对其结果运行 IO 操作 f。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。由于 EIO ε 操作可能抛出 ε 类型的异常,任务结果是 Except ε α。

🔗定义
IO.mapTask.{u_1} {α : Type u_1} {β : Type} (f : α → IO β) (t : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except IO.Error β))
IO.mapTask.{u_1} {α : Type u_1} {β : Type} (f : α → IO β) (t : Task α) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except IO.Error β))

创建一个新任务,等待 t 完成,然后对其结果运行 IO 操作 f。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。由于 IO 操作可能抛出 IO.Error 类型的异常,任务结果是 Except IO.Error α。

🔗定义
BaseIO.mapTasks.{u_1} {α : Type u_1} {β : Type} (f : List α → BaseIO β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task β)
BaseIO.mapTasks.{u_1} {α : Type u_1} {β : Type} (f : List α → BaseIO β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task β)

创建一个新任务,等待列表 tasks 中的所有任务完成,然后对它们的结果运行 IO 操作 f。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
EIO.mapTasks.{u_1} {α : Type u_1} {ε β : Type} (f : List α → EIO ε β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except ε β))
EIO.mapTasks.{u_1} {α : Type u_1} {ε β : Type} (f : List α → EIO ε β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except ε β))

创建一个新任务,等待列表 tasks 中的所有任务完成,然后对它们的结果运行 EIO ε 操作 f。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
IO.mapTasks.{u_1} {α : Type u_1} {β : Type} (f : List α → IO β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except IO.Error β))
IO.mapTasks.{u_1} {α : Type u_1} {β : Type} (f : List α → IO β) (tasks : List (Task α)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except IO.Error β))

EIO.mapTasks 的 IO 特化版本。

🔗不透明定义
BaseIO.bindTask.{u_1} {α : Type u_1} {β : Type} (t : Task α) (f : α → BaseIO (Task β)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task β)
BaseIO.bindTask.{u_1} {α : Type u_1} {β : Type} (t : Task α) (f : α → BaseIO (Task β)) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task β)

创建一个新任务,等待 t 完成,对其结果运行 IO 操作 f,然后以所得任务继续执行。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动这个新任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
EIO.bindTask.{u_1} {α : Type u_1} {ε β : Type} (t : Task α) (f : α → EIO ε (Task (Except ε β))) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except ε β))
EIO.bindTask.{u_1} {α : Type u_1} {ε β : Type} (t : Task α) (f : α → EIO ε (Task (Except ε β))) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except ε β))

创建一个新任务,等待 t 完成,对其结果运行 EIO ε 操作 f,然后以所得任务继续执行。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动这个新任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。由于 EIO ε 操作可能抛出 ε 类型的异常,任务结果是 Except ε α。

🔗定义
IO.bindTask.{u_1} {α : Type u_1} {β : Type} (t : Task α) (f : α → IO (Task (Except IO.Error β))) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except IO.Error β))
IO.bindTask.{u_1} {α : Type u_1} {β : Type} (t : Task α) (f : α → IO (Task (Except IO.Error β))) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO (Task (Except IO.Error β))

创建一个新任务,等待 t 完成,对其结果运行 IO 操作 f,然后以所得任务继续执行。新任务的优先级为 prio。

运行所得的 BaseIO 操作会立即启动这个新任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。由于 IO 操作可能抛出 IO.Error 类型的异常,任务结果是 Except IO.Error α。

🔗定义
BaseIO.chainTask.{u_1} {α : Type u_1} (t : Task α) (f : α → BaseIO Unit) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO Unit
BaseIO.chainTask.{u_1} {α : Type u_1} (t : Task α) (f : α → BaseIO Unit) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : BaseIO Unit

创建一个新任务,等待 t 完成,然后对其结果运行 IO 操作 f。新任务的优先级为 prio。

这是忽略结果值的 BaseIO.mapTask 版本。

运行所得的 BaseIO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
EIO.chainTask.{u_1} {α : Type u_1} {ε : Type} (t : Task α) (f : α → EIO ε Unit) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : EIO ε Unit
EIO.chainTask.{u_1} {α : Type u_1} {ε : Type} (t : Task α) (f : α → EIO ε Unit) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : EIO ε Unit

创建一个新任务,等待 t 完成,然后对其结果运行 EIO ε 操作 f。新任务的优先级为 prio。

这是忽略结果值的 EIO.mapTask 版本。

运行所得的 EIO ε 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止该操作或让它对最后一个引用被丢弃作出其他响应,act 应通过 IO.checkCanceled 显式检查取消。

🔗定义
IO.chainTask.{u_1} {α : Type u_1} (t : Task α) (f : α → IO Unit) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : IO Unit
IO.chainTask.{u_1} {α : Type u_1} (t : Task α) (f : α → IO Unit) (prio : Task.Priority := Task.Priority.default) (sync : Bool := false) : IO Unit

创建一个新任务,等待 t 完成,然后对其结果运行 IO 操作 f。新任务的优先级为 prio。

这是忽略结果值的 IO.mapTask 版本。

运行所得的 IO 操作会立即启动任务。与通过 Task.spawn 创建的纯任务不同,即使任务的最后一个引用被丢弃,此函数创建的任务仍会运行。如果此时应终止 act 或让它对最后一个引用被丢弃作出其他响应,应通过 IO.checkCanceled 显式检查取消。

21.11.4. 取消与状态🔗

非纯任务应使用 IO.checkCanceled 响应取消;取消可能由 IO.cancel 引发,也可能在任务的最后一个引用被丢弃时发生。 纯任务会在取消时自动终止。

🔗不透明定义
IO.cancel.{u_1} {α : Type u_1} : Task α → BaseIO Unit
IO.cancel.{u_1} {α : Type u_1} : Task α → BaseIO Unit

请求协作式取消任务。任务必须显式调用 IO.checkCanceled 才会响应取消。

🔗不透明定义

检查当前任务的取消标志是否已因调用 IO.cancel 或丢弃任务的最后一个引用而置位。

🔗定义
IO.hasFinished.{u_1} {α : Type u_1} (task : Task α) : BaseIO Bool
IO.hasFinished.{u_1} {α : Type u_1} (task : Task α) : BaseIO Bool

检查任务是否已执行完毕;一旦完成,调用 Task.get 会立即返回。

🔗不透明定义

返回 Lean 运行时任务管理器中任务的当前状态。

对于派生自 Promise 的任务,应将 waiting 和 running 状态视为等价。

🔗归纳类型

Lean 运行时任务管理器中 Task 的当前状态。

IO.TaskState.waiting : IO.TaskState

Task 正在等待运行。

它可能正在等待依赖项完成,也可能位于任务管理器队列中等待可用的运行线程。

IO.TaskState.running : IO.TaskState

Task 正在某个线程上运行;对于 Promise,则表示正在等待调用 IO.Promise.resolve。

IO.TaskState.finished : IO.TaskState

Task 已经运行完毕,结果可用;对此任务调用 Task.get 或 IO.wait 不会阻塞。

🔗不透明定义

返回调用线程的线程 ID。

21.11.5. 承诺🔗

承诺表示一个将在未来提供的值。 提供该值称为兑现承诺。 承诺创建后,可以像其他值一样存入数据结构或四处传递;尝试读取它时会阻塞,直至它兑现。

🔗结构体
IO.Promise (α : Type) : Type
IO.Promise (α : Type) : Type

Promise α 允许创建一个 Task α,其值稍后通过调用 resolve 提供。

典型用法如下:

  1. let promise ← Promise.new 创建一个承诺

  2. promise.result? : Task (Option α) 现在可以四处传递

  3. promise.result?.get 阻塞,直到承诺得到解析

  4. promise.resolve a 解析该承诺

  5. promise.result?.get 现在返回 some a

如果承诺在从未解析的情况下被丢弃,promise.result?.get 将返回 none。 其他处理方式请参阅 Promise.result!/resultD。

🔗不透明定义
IO.Promise.new {α : Type} [Nonempty α] : BaseIO (IO.Promise α)
IO.Promise.new {α : Type} [Nonempty α] : BaseIO (IO.Promise α)

创建一个新的 Promise。

🔗定义
IO.Promise.isResolved {α : Type} (promise : IO.Promise α) : BaseIO Bool
IO.Promise.isResolved {α : Type} (promise : IO.Promise α) : BaseIO Bool

检查承诺是否已经解析,即访问 result* 是否会立即返回。

🔗不透明定义
IO.Promise.result? {α : Type} (promise : IO.Promise α) : Task (Option α)
IO.Promise.result? {α : Type} (promise : IO.Promise α) : Task (Option α)

与 Promise.result 类似,但如果承诺在从未解析的情况下被丢弃,则解析为 none。

🔗定义
IO.Promise.result! {α : Type} (promise : IO.Promise α) : Task α
IO.Promise.result! {α : Type} (promise : IO.Promise α) : Task α

Promise 的结果任务。

该任务会阻塞,直到调用 Promise.resolve。如果承诺在从未解析的情况下被丢弃,对任务求值将引发恐慌;不使用致命恐慌时,则会永远阻塞。由于 Promise.result! 是纯值,可能无法准确得知其求值时点,因此任何可能对其求值 Promise.result! 的承诺都必须最终得到解析。如有疑问,应始终优先使用 Promise.result? 显式处理被丢弃的承诺。

🔗定义
IO.Promise.resultD {α : Type} (promise : IO.Promise α) (dflt : α) : Task α
IO.Promise.resultD {α : Type} (promise : IO.Promise α) (dflt : α) : Task α

与 Promise.result 类似,但如果承诺在从未解析的情况下被丢弃,则解析为 dflt。

🔗不透明定义
IO.Promise.resolve {α : Type} (value : α) (promise : IO.Promise α) : BaseIO Unit
IO.Promise.resolve {α : Type} (value : α) (promise : IO.Promise α) : BaseIO Unit

解析一个 Promise。

只有第一次调用此函数会产生效果。

21.11.6. 任务间通信🔗

除本节介绍的类型与操作外,IO.Ref 也可用作锁。 取走引用(使用 take)会使其他线程在读取时阻塞,直到再次用 set 设置该引用。 这种模式在引用单元一节中介绍。

21.11.6.1. 通道🔗

导入 Std.Sync.Channel 后即可使用本节中的类型与函数。

🔗结构体
Std.Channel (α : Type) : Type
Std.Channel (α : Type) : Type

一种多生产者、多消费者的 FIFO 通道,既支持有界和无界缓冲,也提供异步 API。使用 Channel.sync 可切换到同步模式。

如果通道需要通过关闭来表示某种完成事件,请改用 Std.CloseableChannel。请注意,Std.CloseableChannel 在某些情况下需要错误处理,因此适用时通常更容易使用 Std.Channel。

🔗定义
Std.Channel.new {α : Type} (capacity : Option Nat := none) : BaseIO (Std.Channel α)
Std.Channel.new {α : Type} (capacity : Option Nat := none) : BaseIO (Std.Channel α)

创建新通道。若:

  • capacity 为 none,通道无界(默认)

  • capacity 为 some 0,发送方与接收方每次都必须会合

  • capacity 为 some n 且 n > 0,通道使用大小为 n 的缓冲区,缓冲区填满后开始阻塞

🔗定义
Std.Channel.send {α : Type} (ch : Std.Channel α) (v : α) : BaseIO (Task Unit)
Std.Channel.send {α : Type} (ch : Std.Channel α) (v : α) : BaseIO (Task Unit)

通过通道发送一个值,并返回一个在传输完成后解析的任务。

🔗定义
Std.Channel.recv {α : Type} [Inhabited α] (ch : Std.Channel α) : BaseIO (Task α)
Std.Channel.recv {α : Type} [Inhabited α] (ch : Std.Channel α) : BaseIO (Task α)

从通道接收一个值,并返回一个在传输完成后解析的任务。请注意,如果通道在传输完成前关闭,该任务可能解析为 none。

🔗不透明定义

ch.forAsync f 对从 ch 接收的每条消息调用 f。

请注意,如果调用此函数两次,每条消息只会到达其中恰好一次调用。

🔗定义
Std.Channel.sync {α : Type} (ch : Std.Channel α) : Std.Channel.Sync α
Std.Channel.sync {α : Type} (ch : Std.Channel α) : Std.Channel.Sync α

此函数不执行任何操作,只是用于方便地公开通道的同步 API。

🔗定义
Std.Channel.Sync (α : Type) : Type
Std.Channel.Sync (α : Type) : Type

一种多生产者、多消费者的 FIFO 通道,既支持有界和无界缓冲,也提供同步 API。此类型只是以阻塞方式使用通道的便捷层,与原通道并无实际区别。

如果通道需要通过关闭来表示某种完成事件,请改用 Std.CloseableChannel.Sync。请注意,Std.CloseableChannel.Sync 在某些情况下需要错误处理,因此适用时通常更容易使用 Std.Channel.Sync。

🔗定义
Std.CloseableChannel (α : Type) : Type
Std.CloseableChannel (α : Type) : Type

一种多生产者、多消费者的 FIFO 通道,既支持有界和无界缓冲,也提供异步 API;使用 CloseableChannel.sync 可切换到同步模式。

此外,与 Std.Channel 不同,Std.CloseableChannel 可在需要时关闭。这在某些情况下会带来错误处理的需要,因此适用时通常更容易使用 Std.Channel。

🔗定义

创建新通道。若:

  • capacity 为 none,通道无界(默认)

  • capacity 为 some 0,发送方与接收方每次都必须会合

  • capacity 为 some n 且 n > 0,通道使用大小为 n 的缓冲区,缓冲区填满后开始阻塞

同步通道也可使用 Lean.Parser.Term.doFor : doElemfor 循环读取。 具体而言,对于每个具有 MonadLiftT BaseIO m 实例的单子 m,以及每个具有 Inhabited α 实例的 α,都存在类型为 ForIn m (Std.Channel.Sync α) α 的实例。

21.11.6.2. 互斥锁🔗

导入 Std.Sync.Mutex 后即可使用本节中的类型与函数。

🔗type
Std.Mutex (α : Type) : Type
Std.Mutex (α : Type) : Type

保护 α 类型共享状态的互斥原语(锁)。

Mutex α 类型类似于 IO.Ref α,但并发访问由互斥锁保护,而不是通过原子指针操作和忙等待来保护。

🔗定义
Std.Mutex.new {α : Type} (a : α) : BaseIO (Std.Mutex α)
Std.Mutex.new {α : Type} (a : α) : BaseIO (Std.Mutex α)

创建一个新的互斥锁。

🔗定义
Std.Mutex.atomically {m : Type → Type} {α β : Type} [Monad m] [MonadLiftT BaseIO m] [MonadFinally m] (mutex : Std.Mutex α) (k : Std.AtomicT α m β) : m β
Std.Mutex.atomically {m : Type → Type} {α β : Type} [Monad m] [MonadLiftT BaseIO m] [MonadFinally m] (mutex : Std.Mutex α) (k : Std.AtomicT α m β) : m β

mutex.atomically k 在锁定互斥锁期间运行 k,使其可访问互斥锁的状态。

如果同一线程已持有底层 BaseMutex,再调用 mutex.atomically 属于未定义行为。如果代码无法避免这种情况,请考虑使用 RecursiveMutex。

🔗定义
Std.Mutex.atomicallyOnce {m : Type → Type} {α β : Type} [Monad m] [MonadLiftT BaseIO m] [MonadFinally m] (mutex : Std.Mutex α) (condvar : Std.Condvar) (pred : Std.AtomicT α m Bool) (k : Std.AtomicT α m β) : m β
Std.Mutex.atomicallyOnce {m : Type → Type} {α β : Type} [Monad m] [MonadLiftT BaseIO m] [MonadFinally m] (mutex : Std.Mutex α) (condvar : Std.Condvar) (pred : Std.AtomicT α m Bool) (k : Std.AtomicT α m β) : m β

mutex.atomicallyOnce condvar pred k 运行 k,并在 condvar 上等待,直到 pred 返回 true。k 和 pred 都可以访问互斥锁的状态。

如果同一线程已持有底层 BaseMutex,再调用 mutex.atomicallyOnce 属于未定义行为。如果代码无法避免这种情况,请考虑使用 RecursiveMutex。

🔗定义
Std.AtomicT (σ : Type) (m : Type → Type) (α : Type) : Type
Std.AtomicT (σ : Type) (m : Type → Type) (α : Type) : Type

AtomicT α m 是一种单子,可在 Mutex α 等互斥原语内部,以外层单子 m 原子地执行。 该操作可以通过 get 和 set 访问互斥锁的状态 α。

21.11.6.3. 条件变量🔗

导入 Std.Sync.Mutex 后即可使用本节中的类型与函数。

🔗定义

条件变量是一种与 BaseMutex 或 Mutex 配合使用的同步原语。

希望修改共享变量的线程必须:

  1. 锁定 BaseMutex 或 Mutex

  2. 操作共享变量

  3. 完成后调用 Condvar.notifyOne 或 Condvar.notifyAll。请注意,这可以在解锁互斥锁之前或之后进行。

若使用 Mutex,等待 Condvar 的线程可以使用 Mutex.atomicallyOnce 等待条件成立。若使用 BaseMutex,则必须:

  1. 锁定 BaseMutex。

  2. 执行以下操作之一:

  • 使用 Condvar.waitUntil 在条件变量上(可能反复)等待,直到条件成立。

  • 按以下步骤手动实现等待:

    1. 检查条件

    2. 调用 Condvar.wait;它会释放 BaseMutex 并暂停执行,直到条件变量收到通知。

    3. 检查条件;如果尚未满足,则继续等待。

🔗不透明定义

创建一个新的条件变量。

🔗不透明定义
Std.Condvar.wait (condvar : Std.Condvar) (mutex : Std.BaseMutex) : BaseIO Unit
Std.Condvar.wait (condvar : Std.Condvar) (mutex : Std.BaseMutex) : BaseIO Unit

等待,直到另一线程调用 notifyOne 或 notifyAll。

🔗不透明定义

唤醒一个正在执行 wait 的其他线程。

🔗不透明定义

唤醒所有正在执行 wait 的其他线程。

🔗定义
Std.Condvar.waitUntil.{u_1} {m : Type → Type u_1} [Monad m] [MonadLiftT BaseIO m] (condvar : Std.Condvar) (mutex : Std.BaseMutex) (pred : m Bool) : m Unit
Std.Condvar.waitUntil.{u_1} {m : Type → Type u_1} [Monad m] [MonadLiftT BaseIO m] (condvar : Std.Condvar) (mutex : Std.BaseMutex) (pred : m Bool) : m Unit

在条件变量上等待,直到谓词为真。