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.