====== Thread(线程) API ====== Thread(线程) API为OpenOS提供了协程的一种变体。线程比基础的协程在很多方面都更高级,并且对很多工作流程而言都更易用。OpenOS线程是自主的,非阻塞的,可分离的进程。 * **自主性:**线程在创建后会立即开始异步执行,无需调用resume。与协程不同,线程在yield后会自恢复,且不会回到创建线程的代码行。使用协程进行编程基于用户显式进行的协作yield和resume调用。然而,在线程中yield或调用`event.pull`,只会暂时阻塞线程,线程将自行继续运行。 * **非阻塞性:**线程可以调用`computer.pullSignal`(或其更高级别的包装,如`event.pull`、`io.pull`等),而不会阻塞主内核进程或任何其他线程。当然线程本身会被阻塞,直到出现信号或超时。线程的计算流程与在命令行中运行相同的代码一样。在幕后,thread(线程)库使用`pullSignal`在线程之间切换,并在适当的时候唤醒线程。这与协程完全不同,在协程中,`computer.pullSignal`会阻塞系统上的所有其他活动,直到出现信号或超时。 * **可分离性:**默认情况下,线程由创建它们的进程拥有(注意线程本身就是一个进程)。拥有线程的进程被称为其父进程。父进程关闭(即执行完毕)时会关闭所有正在运行的子线程(将子线程join到父进程)。但是在以下情况下,进程不必关闭线程: * 线程从父进程上分离。参见`t:detach()`。请注意线程也可以附着于其他父进程,参见`t:attach()`。 * 父进程抛出了异常或调用了`os.exit`,此时所有附着在上面的线程都会被杀死。请注意,重启也会杀死所有线程。 * 线程被杀死,参见`t:kill()`。 * 线程被手动暂停,参见`t:suspend()`。 ====独立事件注册==== 每个线程都维护了一套独立的事件注册机制;不继承也不分享。线程中创建的所有事件注册记录(即侦听器或定时器)都只从属于此线程。 * 线程结束时,其所有事件注册记录都会随之结束。 * `suspended`(暂停的)线程会忽略事件(参见`t:status() suspended`) * 一个线程不可以访问/移除/交互其他线程的事件注册记录。 * 被推送的事件对系统中的所有运行中线程可见。 * 两个独立的线程可以对同一个事件`event.pull`,每个线程都会独立观测到此事件。 ===== 概述 ===== 线程主要有两种用途是其他选择无法提供: - 你需要编写一个函数,其中进行了阻塞式调用,但不阻塞程序其它部分的运行。 - 你需要编写长期运行的后台函数,而不想手动管理其yield与resum。 ===== 函数 ===== 此处定义了两类API。 1. [[api:thread:zh#线程API|线程]]API,或者说静态函数,由`require("thread")`提供。 2. [[api:thread:zh#线程句柄API|线程句柄]]API,或者说适用于`thread.create`创建的线程_对象_的api。在此文档中这些线程句柄将会仅以`t`表示。 ===== 线程API ===== - `thread.create(thread_proc: function[, ...]): table` 开启一个执行函数`thread_proc`的新线程,并返回其线程句柄,参见[[api:thread:zh#线程句柄API|线程句柄API]]。此方法可接收可选的`...`参数,这些参数将会被传递给`thread_proc`。 线程将自动开始运行。 ```lua local thread = require("thread") print("Main program start") thread.create(function(a, b) print(a) os.sleep() print(b) end, 'A', 'B') print("Main program end") ``` 输出: ``` Main program start A Main program end B ``` - `thread.waitForAll(threads: table[, timeout: number]): boolean, string` 等待给出的`threads`全部执行完成。此阻塞式操作可以在给出的`timeout`秒超时时间后返回,若给出此值的话。返回值为是否成功,若失败还会返回报错信息。线程可以在多种条件下被认定为”执行完成“,详见`t:join()`。 ```lua local thread = require("thread") print("Main program start") local t = thread.create(function(a, b) print(a) os.sleep() print(b) end, 'A', 'B') thread.waitForAll({t}) print("Main program end") ``` 输出: ``` Main program start A B Main program end ``` - `thread.waitForAny(threads: table[, timeout: number): boolean, string` 等待给出线程中的某一个完成,其他方面`thread.waitForAll()`一致。 ```lua local thread = require("thread") print("Main program start") local t1 = thread.create(function(a, b) print(a) os.sleep() print(b) end, 'A', 'B') local t2 = thread.create(function(c, d) print(c) os.sleep() os.sleep() print(d) end, 'C', 'D') thread.waitForAny({t1, t2}) print("Main program end") ``` 输出: ``` Main program start A C B Main program end D ``` 请注意线程恢复的顺序并未指定,此样例中也有可能在输出"Main program end"之前就输出"D"。 - `thread.current(): table` 返回当前线程对象`t`。init进程不代表任何线程,此函数在init进程内而且不在任何线程内调用时不会有返回值。 ===== 线程句柄API ===== - `t:resume(): boolean, string` 恢复(或者说解冻)一个暂停的线程。返回操作是否成功,若失败额外返回一条报错信息。线程在创建时直接就是运行状态,因此在基本的线程工作流程中完全用不到调用`t:resume()`。“运行中”的线程会自动继续,直到执行完毕。只有在需要恢复被暂停的(`t:suspend()`)线程时才需要`t:resume()`。**请注意,**因为你不是直接恢复线程,线程抛出的一切异常都会被线程运行库吸收,不会暴露给你的进程。 * 目前还无法为线程挂载异常处理函数,但是现在可以用`event.onError`将报错信息输出到"/tmp/event.log"。请注意目前硬中断异常只会抛出一次,并且在抛出硬中断时,带线程的进程执行的行为仍未指定。目前,任何一个线程或父进程都可能接收到异常。这些细节并不是线程规范的一部分,任何实现细节的部分都可能在以后发生变化。 - `t:suspend(): boolean, string` 暂停(或者说冻结)一个运行中的线程。返回操作是否成功,若失败额外返回一条报错信息。“暂停的”线程不会自动唤醒,也不会随其父进程(若附有的话)结束而结束。暂停的线程会忽略出现的事件,代表此线程中定义的任何事件侦听器和定时器都不会响应事件通知。请注意线程不会缓存事件信号,暂停的线程可能会错失其等待的信号。例如,假设一个线程最近一次使用了`event.pull("modem_message")`等待信号,并且被“暂停”了,此时电脑收到了一条"modem_message"。这样线程将错失事件,永远不知道发生了什么。请注意如果你暂停了一条正在被阻塞以等待事件的线程,线程下次被唤醒时会接收到什么事件是未指定的。 暂停当前线程就会导致线程立刻yield,在其他地方显式调用`t:resume()`之前也不会恢复。 **`t:resume`和`t:suspend`的特别注释** 不要把这两个方法当作`coroutine.resume()`和`coroutine.yield()`。这两个方法是间接的,而且线程是独自异步开始或停止运行的。请将这一特性与协程方法相对比,协程方法是直接的,而且会立即唤起或离开协程的执行。参考这些样例: ```lua local thread = require("thread") local t -- 此样例需要t的upvalue t = thread.create(function() print("start") thread.current():suspend() print("after suspend") os.sleep() print("after sleep") end) ``` 输出: ``` start ``` ```lua local thread = require("thread") local t -- this example needs an upvalue to t t = thread.create(function() print("start") thread.current():suspend() print("after suspend") os.sleep() print("after sleep") end) print("outside thread create") t:resume() print("after resume") ``` 输出: ``` start outside thread create after suspend after resume after sleep ``` - `t:kill()` 给它一刀!将线程杀死。线程将会被终止,不能再继续执行其线程函数。它的所有事件注册记录也都会随之结束。请注意核心底层实际上是Lua协程,协程不是抢占式线程。因此线程的结束点是可知的,意味着你可以预测线程结束的准确位置。参考此样例: ```lua local thread = require("thread") local t = thread.create(function() while true do print("running") print("still running") os.sleep() print("after sleep") end print("unreachable code") end) print("before kill") t:kill() print("after kill") ``` 输出: ``` running still running before kill after kill ``` - `t:status(): string` 以字符串形式返回线程状态。 * **"running" 运行** 运行中的线程会在yield和阻塞式调用后继续运行(自动重新开始活动),直到其线程函数退出。这是创建后线程的初始与默认状态。线程就算在阻塞或不活动的状态下也会保持“运行”状态。运行中的线程可以被暂停(`t:suspend()`)或杀死(`t:kill()`),但是不可以恢复(`t:resume()`)。运行中的线程将会阻塞`t:join()`的调用,也会阻塞其父进程的关闭。与协程不同,协程在未执行时即为“暂停”状态,而线程就算在等待事件时也是“运行”状态。 * **"suspended" 暂停** 被暂停的线程会保持暂停状态,永远不会自动恢复执行其线程函数。暂停的线程会在其附着的父进程关闭时或你试图`t:join()`它时自动被杀死。暂停的线程会忽略事件信号,此线程的上下文所创建的,或其创建的任何子线程所创建的事件注册记录,也都会忽略事件信号。暂停线程的子线程表现会与暂停一样,即使其状态为“运行”。暂停的线程可以恢复(`t:resume()`)或者杀死(`t:kill()`),但不可以暂停(`t:suspend()`)。 * **"dead" 死亡** 死亡的线程是结束了或放弃了自己的运行或被终止的线程。它不可被恢复(`t:resume()`)也不可被暂停(`t:suspend()`)。死亡的线程不会阻塞父进程的关闭。杀死死亡的线程不会报错,而是什么也不做。 ** 状态样例 ** ```lua local thread = require("thread") local t = thread.create(function() print("before sleep") os.sleep() print("after sleep") end) print(t:status()) ``` 输出: ``` before sleep running after sleep ``` ```lua local thread = require("thread") local t = thread.create(function() print("before sleep") os.sleep() print("after sleep") end) t:suspend() print(t:status()) os.sleep(10) print(t:status()) t:resume() print("after resume") print(t:status()) ``` 输出: ``` before sleep suspended suspended after resume dead ``` - `t:attach([level: number]): boolean, string` 将一个线程附着到一个进程,通常称为子线程或附着线程。`level`为可选参数,可用于获取父进程,传递0或`nil`则使用当前运行进程。在最初创建线程时,线程已经被附着于当前进程。若`level`指向了不存在的线程,则此方法会返回`nil`与一条报错信息,否则返回true。附着的线程将会阻塞其父进程的关闭,直到线程死亡(或者被杀死,或者父进程放弃执行)。 - `t:detach(): table, string` 将一个线程从其父进程(若有)上解除附着。若未执行动作则返回`nil`与一条报错信息,否则返回自身(以便于你想在同一行中创建一个线程并将其解除附着)。解除附着的线程会继续执行,直到电脑关机或重启,或者线程死亡。 ```lua local detached_thread = thread.create(function() end):detach() ``` - `t:join([timeout: number]): boolean, string` 阻塞调用者,直到`t`不再运行。或者在到达了`timeout`秒的超时时间后返回`false`(可选)。在`t:join()`的调用结束后线程的状态将为“死亡”。任一下列情况都会导致`join`结束并停止阻塞调用者: * 线程继续运行,直到其从线程函数中返回。 * 线程被放弃,或抛出了未被捕获的异常。 * 线程被暂停。 * 线程被杀死 调用`thread.waitForAll({t})`在功能上与调用`t:join()`一致。在进程结束时会对其子线程调用`thread.waitForAll`(若有)。子线程也通过同样的机制阻塞其父进程。 ===== 线程异常样例 ===== 此样例演示了线程抛出异常时会发生什么。线程在抛出未捕获的异常时会停止执行并进入“死亡”状态。报错信息将不会输出到stdout或stderr(详见`t:resume()`)。 ```lua local thread = require("thread") local tty = require("tty") print("p start") local reader = thread.create(function() print("reader start") error("thread abort") -- 抛出一个异常 print("reader done") end) print("p end", reader:status()) ``` 输出: ``` p start reader start p end dead ``` ===== 线程中断处理函数样例 ===== 此样例演示了你该如何注册处理软中断(^c)的函数,用于关闭文件句柄、释放资源等等,然后退出整个程序。 ```lua local thread = require("thread") local event = require("event") local cleanup_thread = thread.create(function() event.pull("interrupted") print("cleaning up resources") end) local main_thread = thread.create(function() print("main program") while true do io.write("input: ") io.read() end end) thread.waitForAny({cleanup_thread, main_thread}) os.exit(0) -- 关闭所有剩余线程 ``` 假设用户按下了^c发送了一次软中断 输出: ``` main program input: ^c cleaning up resources ``` ===== 非阻塞式线程Yield/Pull操作样例 ===== 此样例演示了现在OpenOS可支持非阻塞式线程。 ```lua local event = require("event") local thread = require("thread") thread.create(function() a,b,c,d,e,f,g = coroutine.yield() print(a,b,c,d,e,f,g) print(event.pull()) end) event.push("custom_event_a") print("done") event.push("custom_event_b", 2) ``` 输出: ``` custom_event_a done custom_event_b 2 ``` 目录 ----------- {{page>api:contents:zh&noheader&noeditbutton&nouser&nofooter}}