Thread(线程) API为OpenOS提供了协程的一种变体。线程比基础的协程在很多方面都更高级,并且对很多工作流程而言都更易用。OpenOS线程是自主的,非阻塞的,可分离的进程。
event.pull
,只会暂时阻塞线程,线程将自行继续运行。computer.pullSignal
(或其更高级别的包装,如event.pull
、io.pull
等),而不会阻塞主内核进程或任何其他线程。当然线程本身会被阻塞,直到出现信号或超时。线程的计算流程与在命令行中运行相同的代码一样。在幕后,thread(线程)库使用pullSignal
在线程之间切换,并在适当的时候唤醒线程。这与协程完全不同,在协程中,computer.pullSignal
会阻塞系统上的所有其他活动,直到出现信号或超时。t:detach()
。请注意线程也可以附着于其他父进程,参见t:attach()
。
os.exit
,此时所有附着在上面的线程都会被杀死。请注意,重启也会杀死所有线程。
t:kill()
。
t:suspend()
。每个线程都维护了一套独立的事件注册机制;不继承也不分享。线程中创建的所有事件注册记录(即侦听器或定时器)都只从属于此线程。
suspended
(暂停的)线程会忽略事件(参见t:status() suspended
)event.pull
,每个线程都会独立观测到此事件。线程主要有两种用途是其他选择无法提供:
此处定义了两类API。
require("thread")
提供。thread.create(thread_proc: function[, ...]): table
开启一个执行函数thread_proc
的新线程,并返回其线程句柄,参见线程句柄API。此方法可接收可选的...
参数,这些参数将会被传递给thread_proc
。 线程将自动开始运行。
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()
。
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()
一致。
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进程内而且不在任何线程内调用时不会有返回值。
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()
。这两个方法是间接的,而且线程是独自异步开始或停止运行的。请将这一特性与协程方法相对比,协程方法是直接的,而且会立即唤起或离开协程的执行。参考这些样例:
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
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协程,协程不是抢占式线程。因此线程的结束点是可知的,意味着你可以预测线程结束的准确位置。参考此样例:
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
以字符串形式返回线程状态。
运行中的线程会在yield和阻塞式调用后继续运行(自动重新开始活动),直到其线程函数退出。这是创建后线程的初始与默认状态。线程就算在阻塞或不活动的状态下也会保持“运行”状态。运行中的线程可以被暂停(t:suspend()
)或杀死(t:kill()
),但是不可以恢复(t:resume()
)。运行中的线程将会阻塞t:join()
的调用,也会阻塞其父进程的关闭。与协程不同,协程在未执行时即为“暂停”状态,而线程就算在等待事件时也是“运行”状态。
被暂停的线程会保持暂停状态,永远不会自动恢复执行其线程函数。暂停的线程会在其附着的父进程关闭时或你试图t:join()
它时自动被杀死。暂停的线程会忽略事件信号,此线程的上下文所创建的,或其创建的任何子线程所创建的事件注册记录,也都会忽略事件信号。暂停线程的子线程表现会与暂停一样,即使其状态为“运行”。暂停的线程可以恢复(t:resume()
)或者杀死(t:kill()
),但不可以暂停(t:suspend()
)。
死亡的线程是结束了或放弃了自己的运行或被终止的线程。它不可被恢复(t:resume()
)也不可被暂停(t:suspend()
)。死亡的线程不会阻塞父进程的关闭。杀死死亡的线程不会报错,而是什么也不做。
状态样例
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
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
与一条报错信息,否则返回自身(以便于你想在同一行中创建一个线程并将其解除附着)。解除附着的线程会继续执行,直到电脑关机或重启,或者线程死亡。
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()
)。
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)的函数,用于关闭文件句柄、释放资源等等,然后退出整个程序。
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
此样例演示了现在OpenOS可支持非阻塞式线程。
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