Landin library reference discuss source

hosted/thread/lock

Hosted targets only.

A lock that threads share state behind, and the record that a thread has started.

Call What it does
lock.new Make a free lock for a local or a field; module storage starts one zeroed.
lock.take Take the lock, waiting while another thread holds it.
lock.take_if_free Take it if it is free, and say whether it did, without waiting.
lock.give Give back a lock this thread took, waking one thread that waits for it.
lock.note_start Record that the program is about to start a thread. The thread facility calls it.
lock.threads_started Whether note_start has been called.

A lock is one u32 cell of core/atomic. Taking a free lock and giving one nobody waits for are one read-modify-write each; a thread that finds it held sleeps in platform/posix's wait on the cell's address, and the give wakes it. What the thread that gave it did while it held it is visible to the thread that takes it next.

It is not reentrant and keeps no owner: a thread that takes a lock it holds waits for ever. A fibre that holds a lock across a switch holds the whole thread, and every other fibre of its round that wants the lock.

It is a module beneath hosted/thread so that hosted/heap can take a lock without the thread facility, which never imports the heap. On Cortex-M0 there is no lock: platform/cortex_m's critical section is one.

Items

public lock: type = lock_value

A lock: one thread at a time holds it, between take and give. A zeroed lock is free, so module storage starts one zeroed and new makes one for a local or a field. It is not reentrant: a thread that takes a lock it holds waits for itself forever. It keeps no owner, so nothing stops a thread giving a lock another holds; do not. Waiting blocks the thread, so a fibre that holds a lock across a switch can hold every other fibre of its round that wants it. Share it by pointer, never by copy.

public give: (held: ptr mut lock) -> none

Give held, which this thread took, back, and wake one thread waiting for it, if any is. What this thread did while it held the lock is visible to the thread that takes it next.

public new: () -> (made: lock)

A free lock.

public note_start: () -> none

Record that the program is about to start a thread. The thread facility calls it before it starts its first thread, on the thread that starts it, and the creation's happens-before edge gives the new thread the record. State that is shared only once there are threads, the heap's among it, then takes its lock; until then it skips it.

public take: (held: ptr mut lock) -> none

Take held, waiting while another thread holds it. What the thread that gave it last did before giving it is visible after this returns.

public take_if_free: (held: ptr mut lock) -> (took: bool)

Take held if no thread holds it, and say whether it did, without waiting.

threads_started

function hosted/thread/lock/lock.ldn:100 ↑ index
public threads_started: () -> (yes: bool)

Whether note_start has been called: whether a thread other than the one the program started on may be running. A C thread calling into Landin under --link=libc is none of the program's and is not counted.

Example program

This complete program is one of the compiler's runtime tests. View source.

The program, 42 lines
compiler/tests/fixtures/runtime/thread-lock-one-thread/main.ldn42 lines
--  The lock and the started flag on the one thread a program starts
--  with.  Nothing here can contend, so every take finds the lock free or
--  is a take_if_free that does not wait; a take that waits, and the give
--  that wakes it, need a second thread.
import hosted/thread/lock

mut shared: lock.lock = zeroed

--  Each check returns its own status, so a failure says which one.
cycle: (held: ptr mut lock.lock, first: i32) -> (status: i32) =
    status = first
    return when not lock.take_if_free(held)
    status = first + 1
    return when lock.take_if_free(held)
    lock.give(held)
    status = first + 2
    lock.take(held)
    return when lock.take_if_free(held)
    lock.give(held)
    status = first + 3
    return when not lock.take_if_free(held)
    lock.give(held)
    status = 0
end cycle

public main: () -> (code: i32) =
    code = 2
    return when lock.threads_started()
    code = cycle(addr shared, 10)
    return when code <> 0
    mut local: lock.lock = lock.new()
    code = cycle(addr local, 20)
    return when code <> 0
    lock.note_start()
    code = 3
    return when not lock.threads_started()
    lock.note_start()
    return when not lock.threads_started()
    code = cycle(addr shared, 30)
    return when code <> 0
    code = 42
end main