**This is an old revision of the document!**

Thread(线程) API

Thread(线程) API为OpenOS提供了协程的一种变体。线程比基础的协程在很多方面都更高级,并且对很多工作流程而言都更易用。OpenOS线程是自主的,非阻塞的,可分离的进程。

  • 自主性:线程在创建后会立即开始异步执行,无需调用resume。与协程不同,线程在yield后会自恢复,且不会回到创建线程的代码行。使用协程进行编程基于用户显式进行的协作yield和resume调用。然而,在线程中yield或调用event.pull,只会暂时阻塞线程,线程将自行继续运行。
  • 非阻塞性:线程可以调用computer.pullSignal(或其更高级别的包装,如event.pullio.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,或者说静态函数,由require("thread")提供。
  2. 线程句柄API,或者说适用于thread.create创建的线程对象的api。在此文档中这些线程句柄将会仅以t表示。

线程API

  • thread.create(thread_proc: function[, ...]): table

    开启一个执行函数thread_proc的新线程,并返回其线程句柄,参见线程句柄API。此方法可接收可选的...参数,这些参数将会被传递给thread_proc。 线程将自动开始运行。

snippet.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()

snippet.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()一致。

snippet.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进程内而且不在任何线程内调用时不会有返回值。

Thread Handle API

  • t:resume(): boolean, string

    Resumes (or thaws) a suspended thread. Returns success and an error message on failure. A thread begins its life already in a running state and thus basic thread workflows will not ever need to call t:resume(). A “running” thread will autonomously continue until it completes. t:resume() is only necessary to resume a thread that has been suspended(t:suspend()). Note that because you are not directly resuming the thread any exceptions thrown from the thread are absorbed by the threading library and not exposed to your process.

    • At this time there is no way to hook in an exception handler for threads but for now event.onError is used to print the error message to “/tmp/event.log”. Please note that currently the hard interrupt exception is only thrown once, and the behavior of a process with threads when a hard interrupt is thrown is unspecified. At this time, any one of the threads or the parent process may take the exception. These details are not part of the specification for threads and any part of this implementation detail may change later.
  • t:suspend(): boolean, string

    Suspends (or freezes) a running thread. Returns success and an error message on failure. A “suspended” thread never autonomously wakes up and dies as soon as its parent process (if attached) closes. A suspended thread ignores events. That means any event listeners or timers created inside the thread will not respond to event notifications. Note that threads do not buffer event signals and a suspended thread may miss event signals it was waiting for. For example, if a thread was last waiting on event.pull("modem_message") and is “suspended” and a “modem_message” is received by the computer then the thread will miss the event and never know it happened. Please note that if you suspend a thread that is blocked waiting for an event, it is unspecified which event the thread will receive when it is next resumed.
    Suspending the current thread causes the thread to immediately yield and does not resume until t:resume() is called explicitly elsewhere.

Special notes about t:resume, t:suspend

Do not think of these methods as coroutine.resume() nor coroutine.yield(). These methods are indirect and a thread will asynchronously start or stop running on its own. Contrast this to coroutine methods which directly and immediately invoke execution or leave execution of a coroutine. Consider these examples:

snippet.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)

Output:

start
snippet.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")

Output:

start
outside thread create
after suspend
after resume
after sleep
  • t:kill()

    Stabby stab! Kills the thread dead. The thread is terminated and will not continue its thread function. Any event registrations it made will die with it. Keep in mind that the core underlying Lua type is a coroutine which is not a preemptive thread. Thus, the thread's stopping points are deterministic, meaning that you can predict exactly where the thread will stop. Consider this example:

snippet.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")

Output:

running
still running
before kill
after kill
  • t:status(): string

    Returns the thread status as a string.

    • “running”

      A running thread will continue (autonomously reactivating) after yields and blocking calls until its thread function exits. This is the default and initial state of a created thread. A thread remains in the “running” state even when blocked or not active. A running thread can be suspended(t:suspend()) or killed (t:kill()) but not resumed(t:resume()). A running thread will block calls to t:join() and block its parent from closing. Unlike a coroutine which appears “suspended” when not executing in this very moment, a thread state remains “running” even when waiting for an event.

    • “suspended”

      A suspended thread will remain suspended and never self resume execution of its thread function. A suspended thread is automatically killed when its attached parent closes or when you attempt to t:join() it. A suspended thread ignores event signals, and any event registrations made from the context of the thread, or any child threads created therein, also ignore any event signals. A suspended thread's children behave as if suspended even if their status is “running”. A suspended thread can be resumed(t:resume()) or killed (t:kill()) but not suspended(t:suspend()).

    • “dead”

      A dead thread has completed or aborted its execution or has been terminated. It cannot be resumed(t:resume()) nor suspended(t:suspend()). A dead thread does not block a parent process from closing. Killing a dead thread is not an error but does nothing.

Status Examples

snippet.lua
local thread = require("thread")
local t = thread.create(function()
  print("before sleep")
  os.sleep()
  print("after sleep")
end)
print(t:status())

Output:

before sleep
running
after sleep
snippet.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())

Output:

before sleep
suspended
suspended
after resume
dead
  • t:attach([level: number]): boolean, string

    Attaches a thread to a process, conventionally known as a child thread or attached thread. level is an optional used to get parent processes, 0 or nil uses the currently running process. When initially created a thread is already attached to the current process. This method returns nil and an error message if level refers to a nonexistent process, otherwise it returns truthy. An attached thread blocks its parent process from closing until the thread dies (or is killed, or the parent process aborts).

  • t:detach(): table, string

    Detaches a thread from its parent if it has one. Returns nil and an error message if no action was taken, otherwise returns self (handy if you want to create and detach a thread in one line). A detached thread will continue to run until the computer is shutdown or rebooted, or the thread dies.

snippet.lua
local detached_thread = thread.create(function() end):detach()
  • t:join([timeout: number]): boolean, string

    Blocks the caller until t is no longer running or (optionally) returns false if timeout seconds is reached. After a call to t:join() the thread state is “dead”. Any of the following circumstances allow join to finish and unblock the caller

    • The thread continues running until it returns from its thread function
    • The thread aborts, or throws an uncaught exception
    • The thread is suspended
    • The thread is killed

      Calling thread.waitForAll({t}) is functionally equivalent to calling t:join(). When a processs is closing it will call thread.waitForAll on the group of its child threads if it has any. A child thread blocks its parent thread by the same machanism.

Thread Exception Example

This example demonstrates what happens when a thread throws an exception. A thread stops executing and becomes “dead” when it throws an uncaught exception. The exception message is not printed to stdout nor stderr (see t:resume()) for details.

snippet.lua
local thread = require("thread")
local tty = require("tty")
 
print("p start")
local reader = thread.create(function()
  print("reader start")
  error("thread abort") -- throws an exception
  print("reader done")
end)
print("p end", reader:status())

Output

p start
reader start
p end	dead

Thread Interrupt Handler Example

This example demonstrates how you would register a function that handles soft interrupts (^c) to close file handles, release resources, etc, and then exit the whole program.

snippet.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) -- closes all remaining threads

Assuming the user presses ^c to send an interrupt Output

main program
input: ^c
cleaning up resources

Thread Yield/Pull Without Blocking Example

This example demonstrates that now OpenOS supports non blocking threads.

snippet.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)

Output

custom_event_a
done
custom_event_b	2

目录