Lean 语言参考手册

21.11. 任务与线程🔗

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

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

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

当任务的最后一个引用被丢弃时,该任务会被取消。 使用 Task.spawn 创建的纯任务会在取消时终止。 使用 IO.asTaskEIO.asTaskBaseIO.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. 优先级🔗

线程调度器使用任务优先级把任务分配给线程。 在 defaultmax 的优先级范围内,高优先级任务总是先于低优先级任务执行。 以 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.mapTask.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.mapTask.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.mapTasksIO 特化版本。

🔗不透明定义
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 的任务,应将 waitingrunning 状态视为等价。

🔗归纳类型

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.getIO.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 α)

创建新通道。若:

  • capacitynone,通道无界(默认)

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

  • capacitysome nn > 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

🔗定义

创建新通道。若:

  • capacitynone,通道无界(默认)

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

  • capacitysome nn > 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。kpred 都可以访问互斥锁的状态。

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

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

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

21.11.6.3. 条件变量🔗

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

🔗定义

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

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

  1. 锁定 BaseMutexMutex

  2. 操作共享变量

  3. 完成后调用 Condvar.notifyOneCondvar.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

等待,直到另一线程调用 notifyOnenotifyAll

🔗不透明定义

唤醒一个正在执行 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

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