Synchronization » CnxSharedLock module

CnxSharedLock provides scoped non-exclusive (shared) locking of any higher-level mutexes (eg CnxMutex, CnxSharedMutex, etc.) 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.

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/SharedLock.h>
#include <Cnx/sync/UniqueLock.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 exclusively 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) {
    // lock `my_thing_mutex` exclusively so we can write to what it's protecting
    __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(void) {
    // lock `my_thing_mutex` non-exclusively (shared) so that we can read from what it's
    // protecting
    __attr(maybe_unused) SharedLock lock = cnx_shared_lock(*my_thing_mutex);
    let val = my_very_important_thing.value;
    return val;
    // `lock` 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);

// multiple threads can all call `get_value_from_my_thing` without blocking eachother, because
// `my_thing_mutex` is a shared mutex and `get_value_from_my_thing` uses a shared lock
my_val = get_value_from_my_thing();
// do something with my_val

let_mut newval = get_value_from_my_thing();
while(newval == my_val) {
    cnx_this_thread_sleep_for(cnx_milliseconds(100));
    newval = get_value_from_my_thing();
}

my_val = newval;
// do something with new value

Classes

struct CnxSharedLock
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.

Typedefs

using CnxSharedLock = struct CnxSharedLock
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.

Functions

void cnx_shared_lock_lock(CnxSharedLock*restrict lock)
Acquires a shared lock on the mutex associated with the given CnxSharedLock
bool cnx_shared_lock_try_lock(CnxSharedLock*restrict lock)
Attempts to acquire a shared lock on the mutex associated with the given CnxSharedLock
bool cnx_shared_lock_try_lock_for(CnxSharedLock*restrict lock, CnxDuration duration)
Attempts to acquire a shared lock on the mutex associated with the given CnxSharedLock
bool cnx_shared_lock_try_lock_until(CnxSharedLock*restrict lock, CnxTimePoint stop_point)
Attempts to acquire a shared lock on the mutex associated with the given CnxSharedLock
void cnx_shared_lock_unlock(CnxSharedLock*restrict lock)
Unlocks the mutex associated with the given CnxSharedLock
CnxSharedMutexInterface cnx_shared_lock_mutex(CnxSharedLock*restrict lock)
Returns the CnxSharedMutexInterface Trait object corresponding with the mutex associated with the given CnxSharedLock
bool cnx_shared_lock_owns_lock(CnxSharedLock*restrict lock)
Returns whether the given CnxSharedLock currently owns a non-exclusive (shared) lock on its associated mutex.
void cnx_shared_lock_free(void* lock)
Destroys the given CnxSharedLock, unlocking its associated mutex if it owns a lock on it.

Defines

#define SharedLock
Declaring a CnxSharedLock as a SharedLock ensures that it will be destroyed when it exits scope, releasing the lock on its associated mutex.
#define cnx_shared_lock(...)
Creates a new CnxSharedLock associated with the given mutex.

Typedef documentation

typedef struct CnxSharedLock CnxSharedLock

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

Function documentation

void cnx_shared_lock_lock(CnxSharedLock*restrict lock)

Acquires a shared lock on the mutex associated with the given CnxSharedLock

Parameters
lock - The CnxSharedLock associated with the mutex to lock non-exclusively

bool cnx_shared_lock_try_lock(CnxSharedLock*restrict lock)

Attempts to acquire a shared lock on the mutex associated with the given CnxSharedLock

Parameters
lock - The CnxSharedLock associated with the mutex to lock non-exclusively
Returns whether locking the mutex was successful

bool cnx_shared_lock_try_lock_for(CnxSharedLock*restrict lock, CnxDuration duration)

Attempts to acquire a shared lock on the mutex associated with the given CnxSharedLock

Parameters
lock - The CnxSharedLock associated with the mutex to lock
duration - The maximum amount of time to spend trying to acquire the lock, after which acquisition will timeout and fail
Returns whether locking the mutex was successful

Attempts to acquire a shared lock on the mutex associated with lock. If the amount of time specified by duration has passed before successfully acquiring the lock, this will timeout and cease acquisition.

bool cnx_shared_lock_try_lock_until(CnxSharedLock*restrict lock, CnxTimePoint stop_point)

Attempts to acquire a shared lock on the mutex associated with the given CnxSharedLock

Parameters
lock - The CnxSharedLock associated with the mutex to lock
stop_point - The point in time after which acquisition should timeout and fail, if the lock has not yet been acquired
Returns whether locking the mutex was successful

Attempts to acquire a shared lock on the mutex associated with lock. If the point in time specified by stop_point has passed before successfully acquiring the lock, this will timeout and cease acquisition.

void cnx_shared_lock_unlock(CnxSharedLock*restrict lock)

Unlocks the mutex associated with the given CnxSharedLock

Parameters
lock - The CnxSharedLock associated with the mutex to unlock

CnxSharedMutexInterface cnx_shared_lock_mutex(CnxSharedLock*restrict lock)

Returns the CnxSharedMutexInterface Trait object corresponding with the mutex associated with the given CnxSharedLock

Parameters
lock - The CnxSharedLock to get the associated mutex of
Returns the CnxSharedMutexInterface Trait object of the mutex associated with the given lock

bool cnx_shared_lock_owns_lock(CnxSharedLock*restrict lock)

Returns whether the given CnxSharedLock currently owns a non-exclusive (shared) lock on its associated mutex.

Parameters
lock - The CnxSharedLock to check if owns a shared lock on its associated mutex
Returns whether lock owns a lock on its mutex

void cnx_shared_lock_free(void* lock)

Destroys the given CnxSharedLock, unlocking its associated mutex if it owns a lock on it.

Parameters
lock - The CnxSharedLock to destroy

Define documentation

#define SharedLock

Declaring a CnxSharedLock as a SharedLock ensures that it will be destroyed when it exits scope, releasing the lock on its associated mutex.

#define cnx_shared_lock(...)

Creates a new CnxSharedLock associated with the given mutex.

Parameters
...
  • mutex - The mutex to acquire a non-exclusive (shared) lock on. Can be any of CnxSharedMutex, or CnxSharedTimedMutex
  • tag - An instance of the tag type specifying construction behavior (see above).
Returns a CnxSharedLock constructed with the specified behavior

Creates a new CnxSharedLock associated with the given mutex. If an instance of a tag type is NOT given to specify construction behavior, this will block until the shared lock is acquired on the given mutex. If an instance of a tag type specifying construction behavior IS given, however, then construction will behave according to the given tag type:

  • A cnx_defer_lock_t: Construct the CnxSharedLock without acquiring the lock on the given mutex. The mutex will need to be manually locked (via the shared lock) later
  • A cnx_adopt_lock_t: The shared lock has already been acquired, so the CnxSharedLock does not need to acquire it. Behavior will otherwise proceed as if no tag type instance were given.
  • A cnx_try_lock_t: Attempt to acquire the shared lock on the given mutex during construction. The CnxSharedLock will need to be queried as to whether it successfully acquired the lock with cnx_shared_lock_owns_lock, but otherwise behavior will proceed as if no tag type instance were given.