CnxSharedLock struct

CnxSharedLock provides scoped non-exclusive (shared) locking of higher-level mutexes (eg CnxSharedMutex, CnxSharedTimedMutex) provided by Cnx. It allows for a simple, concise way to acquire the shared lock on a mutex and ensure that lock is released appropriately when the CnxSharedLock exits scope.

To ensure that a CnxSharedLock is appropriately destroyed when it leaves scope, releasing the lock on its associated mutex, declare it as a SharedLock.

A CnxSharedLock should never be copied, to do so is undefined behavior. A CnxSharedLock may be moved into a new scope with move (eg. passing it to a function as a parameter), however. If this is done though, the receiving function will need to either move the parameter into a local variable declared as a SharedLock, or manually unlock the mutex before it returns.

Example:

// MyThing.h
#include <Cnx/sync/SharedMutex.h>
static MyType my_very_important_thing;
static CnxSharedMutex* my_thing_mutex;

void init_my_thing(void);
void update_my_thing(u64 value);
u64 get_value_from_my_thing(void);

// MyThing.c
#include <Cnx/Allocators.h>
#include <Cnx/sync/UniqueLock.h>
#include <Cnx/sync/SharedLock.h>
#include "MyThing.h"
void init_my_thing(void) {
    if(my_thing_mutex == nullptr) {
        my_thing_mutex = cnx_allocator_allocate_t(CnxSharedMutex, DEFAULT_ALLOCATOR);
        *my_thing_mutex = cnx_shared_mutex_new();

        {
            // use a `CnxUniqueLock` to lock `my_thing_mutex`.
            // mark `lock` as `maybe_unused` so we don't get warnings about an unused variable
            __attr(maybe_unused) UniqueLock lock = cnx_unique_lock(*my_thing_mutex);
            my_very_important_thing = {
                // important intialization
            };
            // `lock` automatically destroyed at scope end, releasing the lock on
`my_thing_mutex`
        }
    }
}

void update_my_thing(u64 value) {
    __attr(maybe_unused) UniqueLock lock = cnx_unique_lock(*my_thing_mutex);
    my_very_important_thing.value = value;
    // `lock` automatically destroyed at scope end, releasing the lock on `my_thing_mutex`
}

u64 get_value_from_my_thing(CnxSharedLock lock) {
    // move lock into lock2 so that the mutex will be unlocked at scope exit
    __attr(maybe_unused) SharedLock lock2 = move(lock);
    let val = my_very_important_thing.value;
    return val;
    // `lock2` automatically destroyed at scope end, releasing the lock on `my_thing_mutex`
}


// do some compute intensive task...
// update the value
update_my_thing(new_value);

SharedLock lock1 = cnx_shared_lock(*my_thing_mutex);
my_val = get_value_from_my_thing(move(lock1));
// do something with my_val

SharedLock lock2 = cnx_shared_lock(*my_thing_mutex);
let_mut newval = get_value_from_my_thing(move(lock2));
while(newval == my_val) {
    cnx_this_thread_sleep_for(cnx_milliseconds(100));
    SharedLock lock = cnx_shared_lock(*my_thing_mutex);
    newval = get_value_from_my_thing(move(lock));
}

my_val = newval;
// do something with new value