Synchronization Primitives
Synchronization primitives are provided via the Ticktimer Server. This includes mutexes, process sleeping, and condvars.
Thread Sleeping
Thread sleeping is a primitive that is implemented by the ticktimer server.
This takes advantage of the fact that a sender will suspend a thread
until a BlockingScalar
message is responded to.
In order to suspend a thread, simply send a BlockingScalar
message
to the ticktimer server with an id
of 1
and an arg1
indicating
the number of milliseconds to sleep.
If you need to sleep for more than 49 days, simply send multiple messages.
Mutex
Mutexes allow for multiple threads to safely access the same data. Xous Mutexes have two paths: A fast path, and a slow path. Non-contended Mutexes traverse the fast path and will not need a context switch. Contended Mutexes will automatically fall back to the slow path.
The core of a Mutex is a single AtomicUsize
. This value is 0 when
the Mutex is unlocked, and nonzero when it is locked.
Mutex: Locking
Locking a Mutex involves a simple try_lock()
operation. In this
operation, atomic instructions are used to replace the value 0
with the value 1
, failing if this is the case.
pub unsafe fn try_lock(&self) -> bool {
self.locked.compare_exchange(0, 1, SeqCst, SeqCst).is_ok()
}
If the Mutex is locked, then the current thread will call yield_slice()
which hands execution to another thread in the current process in the hope
that the other thread will release its mutex.
This currently occurs three times.
If the lock still cannot be locked, then it is "poisoned". Instead of
swapping 0
for 1
, the thread does an atomic Add of 1
to the current
value. If the resulting value is 1
then the lock was successfully
obtained and execution may continue as normal.
However, if the value is not 1
then the process falls back to the Slow Path. This involves sending a BlockingScalar
to the ticktimer
server with an id
of 6
and arg1
set to the address of the Mutex.
Mutex: Unlocking
Unlocking a Mutex on the Fast Path simply involves subtracting 1
from
the Mutex. If the previous value was 1
then there were no other
threads waiting on the Mutex.
Otherwise, send a BlockingScalar
to the ticktimer server with
an id
of 7
and arg1
set to the address of the Mutex.
Condvar
Condvar is Rust's name for "conditional variables". Broadly speaking, they are instances where one thread takes an area of memory and says "Wake me up sometime in the future." A different thread can then say "Wake up one other thread that's waiting on this object." Or it can say "Wake up all other threads that are waiting on this object."
Condvar: Waiting for a Condition
To suspend a thread until a condition occurs, or until a timeout hits,
allocate an area of memory for the condvar. Then send a BlockingScalar
message to the ticktimer server with an id
of 8
. Set arg1
to the
address of the condvar.
In order to add a timeout, set arg2
to the number of milliseconds to
wait for. Times longer than 49 days are not supported, so multiple
calls will be required. If no timeout is required, pass 0
for arg2
.
Condvar: Signaling Wakeups
To wake up another thread, send a BlockingScalar
message to the ticktimer
server with an id
of 9
, and set arg1
to the address of the condvar.
arg2
should contain the number of blocked threads to wake up.
In order to wake only one thread, pass 1
.