silk.concurrency.thread_native.
Operating-system threads that each run one Transferable closure and return its result.
When to use#
Use NativeThread.spawn to run independent work on a new native thread, and
JoinHandle.join to wait for its result. For many small tasks, use silk.concurrency.thread_pool.
Details#
The closure and its result must be Transferable. The closure must be detached: it owns all
that it captures, and borrows nothing from the caller. Each thread runs its closure as the body
of one transferable Execution package (EXEC-010). Thus one non-generic C entry point starts
closures of any type.
Threads use pthread_create on Darwin and GNU libc targets, and wasi-threads on wasm32 with
threading = "multi". A native thread gets an 8 MiB stack, the default main-thread stack on
Darwin and Linux, so work that fits on the main thread fits on a spawned one. The platform's
own thread default is smaller on Darwin (512 KiB). A wasm32 thread gets the 1 MiB stack its
runtime gives every thread.
Errors#
SpawnError if the system cannot make the thread. OutOfMemoryError if the start or result
storage cannot be allocated.
Gotchas#
Dropping a
JoinHandlewithout a join waits for the thread. Thus the result storage that the handle owns lives longer than each write to it.A fatal trap on any thread ends the whole process.
Examples#
Compute on another thread#
import silk.mem.allocator { Allocator, OutOfMemoryError }
import silk.core.effect { Effect }
import silk.concurrency.thread_native { NativeThread, SpawnError }
fn sum(limit: i32) -> i32 {
let mut total = 0
let mut index = 1
while index <= limit {
total = total + index
index = index + 1
}
return total
}
effect fn program() -> i32
! OutOfMemoryError | SpawnError {
let mut allocator = Allocator.systemAllocatorProvider()
// The closure owns what it captures, so it can run on the new thread.
let limit = 100
let handle = run NativeThread.spawn(
fn() -> i32 {
return sum(limit)
}
)
|> Effect.provideMut<Allocator>(&mut allocator)
// Other work runs here while the thread computes.
if handle.join() != 5050 {
return 1
}
return 0
}
effect fn failed<E>(error: E) -> i32 {
drop error
return 1
}
pub fn main() -> i32 {
return run Effect.catchAll(program(), failed)
}Import as NativeThread with import silk.concurrency.thread_native { NativeThread }.
Public declarations: 3.
SpawnError#
pub struct SpawnErrorThe thread could not be created, for example because a resource limit was reached.
Field code#
pub code: i32The error number pthread_create returned.
Implementation Debug for SpawnError#
impl Debug for SpawnErrorOperation debug#
effect fn debug(self: &Self, formatter: &mut Formatter) -> () ! WriterError ? &mut WriterWrites the fields, as SpawnError { code: ... }.
JoinHandle#
pub struct JoinHandle<T>The owner of one running native thread and the storage for its result.
Gotchas#
Dropping a handle without calling JoinHandle.join waits for the thread and drops its result.
Thus the result storage that the handle owns lives longer than each write to it.
Implementation JoinHandle<T>#
impl<T> JoinHandle<T>Method join#
pub fn join(self: Self) -> TWaits for the thread to finish and returns the value its closure produced.
Implementation Drop for JoinHandle<T>#
impl<T> Drop for JoinHandle<T>Operation drop#
fn drop(self: &mut JoinHandle<T>) -> ()NativeThread#
pub struct NativeThreadThe owner of native thread creation.
When to use#
Use NativeThread.spawn to run independent work on a new operating-system thread, and
JoinHandle.join to wait for its result. For many small tasks, use silk.concurrency.thread_pool.
Details#
The closure and its result must be Transferable. The closure must be detached: it owns all
that it captures, and borrows nothing from the caller. Threads use pthread_create on Darwin
and GNU libc targets, and wasi-threads on wasm32 with threading = "multi". A native thread
gets an 8 MiB stack, the default main-thread stack on Darwin and Linux, so work that fits on the
main thread fits on a spawned one. A wasm32 thread gets the 1 MiB stack that its runtime gives
every thread.
Gotchas#
A fatal trap on any thread ends the whole process.
Implementation NativeThread#
impl NativeThreadAssociated function processorCount#
pub fn processorCount() -> usizeReturns the number of processors online, at least one.
Details#
Use it to size a worker pool. The operating system can still run other work on those processors.
Associated function yieldNow#
pub fn yieldNow() -> ()Offers the rest of the current thread's time slice to other runnable threads.
Associated function spawn#
pub effect<'static> fn spawn<
T: 'static + Transferable,
F: 'static + once fn() -> T + Intrinsic.Detached + Transferable
>(work: F) -> JoinHandle<T>
! OutOfMemoryError | SpawnError
? &mut AllocatorStarts work on a new native thread and returns the handle that joins it.
Details#
The thread's start storage and result storage come from the selected Allocator. Creation
failure reports SpawnError and drops work without running it.
Associated function runStarted#
pub unsafe fn runStarted(argument: ?*mut u8) -> ()Runs the thread a wasm32 runtime started with argument.
Details#
Only the wasm32 runtime calls this, on the new thread, with the argument spawn passed to the
runtime's thread creation. A wasm32 module roots no standard-library export of its own, so the
runtime's entry module exports the thread entry and forwards here.
Category: Concurrency.
Profiles: aarch64-apple-darwin, aarch64-unknown-linux-gnu, aarch64-unknown-linux-gnu-no-libc, x86_64-unknown-linux-gnu, x86_64-unknown-linux-gnu-no-libc, wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2.