Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
api:thread [2017/06/11 09:05]
vexatos
api:thread [2019/12/06 14:15]
jasonc [Thread API]
Line 2: Line 2:
 The Thread API provides a variation of coroutines for openos. A thread is superior to basic coroutines in many ways and, for many workflows, is easier to work with. An openos thread is an autonomous non-blocking detachable process. The Thread API provides a variation of coroutines for openos. A thread is superior to basic coroutines in many ways and, for many workflows, is easier to work with. An openos thread is an autonomous non-blocking detachable process.
  
-* **Autonomous**:​ Threads asynchronously begin execution immediately after creation without needing to call resume. Unlike coroutines, threads self-resume after yielding. Also unlike coroutines, threads ​do no yield back to the line of code where they were created. ​On the hand, coroutine design takes into account ​the cooperative yield and resume calls made explicitly by the user. Calling ​`coroutine.yieldin a thread ​interacts with the thread identically to how `coroutine.yield` interacts with scripts you would run from the command line, that is, your thread is blocked until a system event occurs, returning execution to the thread.+* **Autonomous**:​ Threads asynchronously begin execution immediately after creation without needing to call resume. Unlike coroutines, threads self-resume after yielding ​and do not yield back to the line of code where they were created. ​Programming with coroutines builds on the cooperative yield and resume calls made explicitly by the user. Yielding or calling ​`event.pullfrom a thread, ​however, only temporarily blocks just that thread ​and will continue on its own.
  
-* **Non-Blocking**:​ Threads can call computer.pullSignal (or any higher level wrapper such as event.pull, io.pull, etc) without blocking the main kernel process nor any other thread. The thread itself is blocked until a signal or timeout occurs. The computational flow of the thread behaves just as if the same code where run in a script from the command line. Behind the scenes, the thread library is using `pullSignal` to swap between threads, and waking threads up when appropriate. This is very much unlike coroutines where blocking calls block all other activity on the system until a signal or timeout occurs. 
  
-* **Detachable**:​ By default, threads are owned by the process in which they are created (note that threads themselves are a process). The owning process is known as the parent process. When a parent process is closing (i.e. it has reached the end of its execution) it will close all of its running child threads. Closing a thread does not mean killing a thread, but rather, it means to `join` the thread. That is, for a parent process to close it must first close all running child threads. A thread is considered a child thread when it is attached to a parent process (a thread is also a process of its own). A process does not have to close a thread when:+* **Non-Blocking**:​ Threads can call `computer.pullSignal` (or any higher level wrapper such as `event.pull`,​ `io.pull`, etc) without blocking the main kernel process nor any other thread. The thread itself is blocked until a signal or timeout occurs. The computational flow of the thread behaves just as if the same code were run in a script from the command line. Behind the scenes, the thread library is using pullSignal to swap between threads, and waking threads up when appropriate. This is very much unlike coroutines where `computer.pullSignal` blocks all other activity on the system until a signal or timeout occurs. 
 + 
 + 
 +* **Detachable**:​ By default, threads are owned by the process in which they are created (note that threads themselves are a process). The owning process is known as the parent process. When a parent process is closing (i.e. it has reached the end of its execution) it will close(i.e. join) all of its running child threads. A process does not have to close a thread when: 
  
   * The thread detaches from the parent process. see `t:​detach()`. Note that a thread can also attach to another parent. see `t:​attach()`   * The thread detaches from the parent process. see `t:​detach()`. Note that a thread can also attach to another parent. see `t:​attach()`
Line 16: Line 19:
   * The thread is manually suspended. see `t:​suspend()`   * The thread is manually suspended. see `t:​suspend()`
  
 +====Event Registration Independence====
 +
 +A thread maintains an independent set of event registrations;​ it does not inherit any and it does not share any. Any event registration made (e.g. listeners or timers) inside a thread belongs to that thread.
 +  * When a thread dies all of its event registrations die with it.
 +  * A `suspended` thread ignores events (see `t:status() suspended`)
 +  * A thread cannot access/​remove/​interfere with another thread'​s event registrations.
 +  * A pushed event is observed by all running threads on the system.
 +  * Two separate threads can `event.pull` for the same event, and each thread will observe the event independently.
 ===== Overview ===== ===== Overview =====
  
Line 21: Line 32:
  
 - You want to write a function that makes blocking calls without blocking the rest of your application. - You want to write a function that makes blocking calls without blocking the rest of your application.
-- You want to run background job that could be long running without ​blocking execution from returning ​to your main program or shell.+- You want a long running ​background function ​without ​having ​to manage yielding and resuming it manually.
  
 ===== Functions ===== ===== Functions =====
Line 27: Line 38:
 There are two sections of API defined here. There are two sections of API defined here.
  
-1. The `threadapi, or the static functions, provided by `require("​thread"​)` +1. The [[api:thread#​Thread_API|thread]] ​api, or the static functions, provided by `require("​thread"​)` 
-2. The `thread handleapi, or the api available the thread _objects_ created by the `thread` ​api. In this documentation, a thread ​handle ​will be denoted by just `t`. The examples also prefer to use `t` for instances of a thread handle.+2. The [[api:​thread#​Thread_Handle_API|thread handle]] api, or the api available the thread _objects_ created by `thread.create`. In this documentation ​these thread ​handles ​will be denoted by just `t`.
  
 ===== Thread API ===== ===== Thread API =====
  
-- `thread.create(thread_proc:​ function[, ...]): ​thread handle`+- `thread.create(thread_proc:​ function[, ...]): ​table`
  
-Starts a new thread executing the function `thread_proc` and returns its thread handle, see [Thread Handle API]. This method takes an optional `...` which is passed to `thread_proc`. The runtime of the thread continues autonomously.+  ​Starts a new thread executing the function `thread_proc` and returns its thread handle, see [[api:​thread#​Thread_Handle_API|Thread Handle API]]. This method takes an optional `...` which is passed to `thread_proc`. The runtime of the thread continues autonomously.
  
 ```lua ```lua
Line 55: Line 66:
 ``` ```
  
-- `thread.waitForAll(threads: ​array of thread handles[, timeout: number) ​returns successerror message`+- `thread.waitForAll(threads: ​table[, timeout: number]): booleanstring`
  
-Waits for all `threads` to complete. This blocking call can return in `timeout` seconds if provided. Returns success and an error message on failure. A thread is "​completed"​ under multiple conditions, see `t:join()` for details. `threads` is treated as an array table.+  ​Waits for the array of `threads` to complete. This blocking call can return in `timeout` seconds if provided. Returns success and an error message on failure. A thread is "​completed"​ under multiple conditions, see `t:join()` for details.
  
 ```lua ```lua
Line 79: Line 90:
 ``` ```
  
-- `thread.waitForAny(threads: ​array of thread handles[, timeout: number) ​returns successerror message`+- `thread.waitForAny(threads: ​table[, timeout: number): booleanstring`
  
-Waits for any single thread complete and is otherwise equivalent to `thread.waitForAll()`+  ​Waits for any single thread ​to complete and is otherwise equivalent to `thread.waitForAll()`
  
 ```lua ```lua
Line 111: Line 122:
 ``` ```
  
-Please note that threads ​do not resume ​in a gaurenteed ​order+Please note that threads resume order is not specified and this example may print "​D"​ before it prints "Main program end" 
 + 
 +- `thread.current():​ table` 
 + 
 +  Returns the current thread `t` object. The init process does not represent a thread and nothing is returned from this method if called from the init process and not inside any thread. 
 + 
  
 ===== Thread Handle API ===== ===== Thread Handle API =====
  
-- `t:​resume() ​returns successerror message`+- `t:resume(): booleanstring`
  
-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.+  ​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.
  
-`t:suspend() returns success, ​error message`+  * 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.
  
-Suspends ​(or freezesa 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 exampleif 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.+- `t:suspend(): booleanstring`
  
-**Special notes about t:resume, t:​suspend** +  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. 
-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 ​this example:+   
 +  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:
  
 ```lua ```lua
Line 131: Line 153:
 t = thread.create(function() t = thread.create(function()
   print("​start"​)   print("​start"​)
-  ​t:suspend()+  ​thread.current():suspend()
   print("​after suspend"​)   print("​after suspend"​)
   os.sleep()   os.sleep()
Line 141: Line 163:
 ``` ```
 start 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"​)
 +```
 +
 +Output:
 +```
 +start
 +outside thread create
 after suspend after suspend
 +after resume
 +after sleep
 ``` ```
  
 - `t:kill()` - `t:kill()`
  
-Stabby stab! Kills the thread dead. The thread is terminated and will not continue its thread function. Any event registrations it made die with it. Keep in mind that the core underlying Lua type is a coroutinewhich 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: ​+  ​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: ​
  
  
Line 173: Line 218:
 ``` ```
  
-- `t:​status() ​returns status ​string`+- `t:status()string`
  
-Returns the thread status as a string.+  ​Returns the thread status as a string.
  
-* **"​running"​** +  ​* **"​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()`). An 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. Consider this example+
  
-* **"​suspended"​** +  ​running thread ​will continue (autonomously reactivating) after yields ​and blocking calls until its thread function ​exitsThis 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 threaded ​will remain suspended ​and never self resume execution of its thread function. ​A suspended thread ​is automatically killed when its attach 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"​** +  * **"​suspended"​** 
-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.+ 
 +  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 ** ** Status Examples **
Line 230: Line 278:
 ``` ```
  
-- `t:​attach([level:​ number]) ​returns successerror message`+- `t:​attach([level:​ number]): booleanstring`
  
-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.+  ​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()`+- `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).+  ​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.
  
 ```lua ```lua
Line 242: Line 290:
 ``` ```
  
-- `t:join()`+- `t:join([timeout: number]): boolean, string`
  
-Blocks the caller until `t` is no longer running. After a call to `t:join()` the thread state is "​dead"​. Any of the following circumstances allow `join` to finish and unblock the caller+  ​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 continues running until it returns from its thread function 
-* The thread aborts, or throws an uncaught exception +  * The thread aborts, or throws an uncaught exception 
-* The thread is suspended +  * The thread is suspended 
-* The thread is killed+  * The thread is killed
  
-Calling `thread.waitForAll({t})` is functionally equivalent to calling `t:​join()`. ​Also, when a processs is closing it will call `thread.waitForAll` on the group of its child threads if it has any. Also, a child threads created inside a thread ​block their parent thread ​in this same manner.+  ​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. child thread ​blocks its parent thread ​by the same machanism.
  
 ===== Thread Exception Example ===== ===== Thread Exception Example =====
Line 267: Line 315:
   print("​reader done")   print("​reader done")
 end) end)
-print("​p end", ​t:status())+print("​p end", ​reader:status())
 ``` ```
  
Line 315: Line 363:
  
 ```lua ```lua
 +local event = require("​event"​)
 local thread = require("​thread"​) local thread = require("​thread"​)
 thread.create(function() thread.create(function()