# NAME

libslack(locker) - abstract locking, rwlocks

# SYNOPSIS

        #include <slack/std.h>
        #include <slack/locker.h>

        typedef struct Locker Locker;
        typedef int lockerf_t(void *lock);

        Locker *locker_create_mutex(pthread_mutex_t *mutex);
        Locker *locker_create_rwlock(pthread_rwlock_t *rwlock);
        Locker *locker_create_debug_mutex(pthread_mutex_t *mutex);
        Locker *locker_create_debug_rwlock(pthread_rwlock_t *rwlock);
        Locker *locker_create(void *lock, lockerf_t *tryrdlock, lockerf_t *rdlock, lockerf_t *trywrlock, lockerf_t *wrlock, lockerf_t *unlock);
        void locker_release(Locker *locker);
        void *locker_destroy(Locker **locker);
        int locker_tryrdlock(Locker *locker);
        int locker_rdlock(Locker *locker);
        int locker_trywrlock(Locker *locker);
        int locker_wrlock(Locker *locker);
        int locker_unlock(Locker *locker);

        int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
        int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
        int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
        int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
        int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);
        int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);
        int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared);
        int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared);

# DESCRIPTION

This module provides an abstraction of thread synchronisation that facilitates the implementation of MT-Disciplined libraries. I'll explain what this means.

Libraries need to be MT-Safe when used in a multi-threaded program. However, most programs are single-threaded, and synchronisation doesn't come for free, so libraries should be Unsafe when used in a single-threaded program. Even in multi-threaded programs, some functions or objects may only be accessed by a single thread, and so they should not incur the expense of synchronisation.

When an object is shared between multiple threads which need to be synchronised, the method of synchronisation must be carefully selected by the client code. There are tradeoffs between concurrency and overhead. The greater the concurrency, the greater the overhead. More locks give greater concurrency, but have greater overhead. Readers/Writer locks can give greater concurrency than Mutex locks, but have greater overhead. One lock for each object may be required, or one lock for all (or a set of) objects may be more appropriate.

Generally, the best synchronisation strategy for a given application can only be determined by testing/benchmarking the written application. It is important to be able to experiment with the synchronisation strategy at this stage of development without pain.

The solution, of course, is to decouple the synchronisation strategy from the library code. To facilitate this, the Locker type and associated functions can be incorporated into library code to provide the necessary flexibility.

The Locker type specifies a lock and a set of functions for manipulating the lock. Arbitrary objects can include a pointer to a Locker object to use for thread synchronisation. Such objects may each have their own lock, by having separate Locker objects, or they may share the same lock by sharing the same Locker object. Only the application developer can determine what is appropriate for each application, on a case by case basis.

MT-Disciplined means that the application developer has a mechanism for specifying the synchronisation requirements to be applied to library code. For more details, see MT-Disciplined - decoupling thread safety (`http://raf.org/papers/mt-disciplined.html`).

This module also provides an implementation of readers/writer locks which are defined in recent standards but may not be on your system yet. The readers/writer lock implementation originally came from code by Bil Lewis and was then completed and made robust.

`Locker *locker_create_mutex(pthread_mutex_t *mutex)`

  Creates a Locker object that will operate on the mutex lock, `mutex`. locker_tryrdlock(3) and locker_trywrlock(3) will call pthread_mutex_trylock(3). locker_rdlock(3) and locker_wrlock(3) will call pthread_mutex_lock(3). locker_unlock(3) will call pthread_mutex_unlock(3). It is the caller's responsibility to initialise `mutex` if necessary before use, and to destroy `mutex` if necessary after use. On success, returns the new Locker object. On error, returns `null` with `errno` set appropriately.

`Locker *locker_create_rwlock(pthread_rwlock_t *rwlock)`

  Creates a Locker object that will operate on the readers/writer lock, `rwlock`. locker_tryrdlock(3) will call pthread_rwlock_tryrdlock(3). locker_rdlock(3) will call pthread_rwlock_rdlock(3). locker_trywrlock(3) will call pthread_rwlock_trywrlock(3). locker_wrlock(3) will call pthread_rwlock_wrlock(3). locker_unlock(3) will call pthread_rwlock_unlock(3). It is the caller's responsibility to initialise `rwlock` if necessary before use, and to destroy `rwlock` if necessary after use. On success, returns the new Locker object. On error, returns `null` with `errno` set appropriately.

`Locker *locker_create_debug_mutex(pthread_mutex_t *mutex)`

  Equivalent to locker_create_mutex(3) except that debug messages are printed to standard output before and after each locking function is called to help locate deadlocks. The debug messages look like:

          [thread id] funcname(mutex address) ...
          [thread id] funcname(mutex address) done

  On success, returns the new Locker. On error, returns `null` with `errno` set appropriately.

`Locker *locker_create_debug_rwlock(pthread_rwlock_t *rwlock)`

  Equivalent to locker_create_rwlock(3) except that debug messages are printed to standard output before and after each locking function is called to help locate deadlocks. The debug messages look like:

          [thread id] funcname(rwlock address) ...
          [thread id] funcname(rwlock address) done

  On success, returns the new Locker. On error, returns `null` with `errno` set appropriately.

`Locker *locker_create(void *lock, lockerf_t *tryrdlock, lockerf_t *rdlock, lockerf_t *trywrlock, lockerf_t *wrlock, lockerf_t *unlock)`

  Creates a Locker object that will operate on the synchronisation variable, `lock`. locker_tryrdlock(3) will call `tryrdlock`. locker_rdlock(3) will call `rdlock`. locker_trywrlock(3) will call `trywrlock`. locker_wrlock(3) will call `wrlock`. locker_unlock(3) will call `unlock`. It is the caller's responsibility to initialise `lock` if necessary before use, and to destroy `lock` if necessary after use. None of the arguments may be `null`. On success, returns the new Locker object. On error, returns `null` with `errno` set appropriately.

`void locker_release(Locker *locker)`

  Releases (deallocates) `locker`. It is the caller's responsibility to destroy the synchronisation variable used by `locker` if necessary.

`void *locker_destroy(Locker **locker)`

  Destroys (deallocates and sets to `null`) `*locker`. Returns `null`. It is the caller's responsibility to destroy the synchronisation variable used by `locker` if necessary.

`int locker_tryrdlock(Locker *locker)`

  Tries to claim a read lock on the synchronisation variable managed by `locker`. See pthread_mutex_trylock(3) and pthread_rwlock_tryrdlock(3) for details. On success, returns `0`. On error, returns the error code from the underlying pthread library function.

`int locker_rdlock(Locker *locker)`

  Claims a read lock on the synchronisation variable managed by `locker`. See pthread_mutex_lock(3) and pthread_rwlock_rdlock(3) for details. On success, returns `0`. On error, returns the error code from the underlying pthread library function.

`int locker_trywrlock(Locker *locker)`

  Tries to claim a write lock on the synchronisation variable managed by `locker`. See pthread_mutex_trylock(3) and pthread_rwlock_trywrlock(3) for details. On success, returns `0`. On error, returns the error code from the underlying pthread library function.

`int locker_wrlock(Locker *locker)`

  Claims a write lock on the synchronisation variable managed by `locker`. See pthread_mutex_lock(3) and pthread_rwlock_wrlock(3) for details. On success, returns `0`. On error, returns the error code from the underlying pthread library function.

`int locker_unlock(Locker *locker)`

  Unlocks the synchronisation variable managed by `locker`. See pthread_mutex_unlock(3) and pthread_rwlock_unlock(3) for details. On success, returns `0`. On error, returns the error code from the underlying pthread library function.

int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr)

  Initialises the readers/writer lock, `rwlock`, with the attributes in `attr`. If `attr` is `null`, `rwlock` is initialised as a process private lock. Note that this is the only option under Linux. On success, returns `0`. On error, returns an error code.

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)

  Destroys the readers/writer lock, `rwlock`, that was initialised by pthread_rwlock_init(3). It is the caller's responsibility to deallocate the memory pointed to by `rwlock` if necessary. On success, returns `0`. On error, returns an error code.

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)

  Claims a read lock on `rwlock`. Multiple threads may hold a read lock at the same time, but only if no thread holds a write lock. On success, returns `0`. On error, returns an error code.

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)

  Attempts to claim a read lock on `rwlock`. On success, returns `0`. On error, returns an error code. If `rwlock` is already locked, returns `EBUSY`.

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)

  Claims a write lock on `rwlock`. Only a single thread may hold a write lock at any point in time. On success, returns `0`. On error, returns an error code.

int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)

  Attempts to claim a write lock on `rwlock`. On success, returns `0`. On error, returns an error code. If `rwlock` is already locked, returns `EBUSY`.

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)

  Unlocks `rwlock`. On success, returns `0`. On error, returns an error code.

int pthread_rwlockattr_init(pthread_rwlockattr_t *attr)

  Initialises the readers/writer lock attribute object, `attr`. On success, returns `0`. On error, returns an error code.

int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr)

  Destroys the readers/writer lock attribute object, `rwlock`, that was initialised by pthread_rwlockattr_init(3). Note that the memory pointed to by `attr` may also need to be deallocated separately. On success, returns `0`. On error, returns an error code.

int pthread_rwlockattr_getpshared(const pthread_rwlockattr_t *attr, int *pshared)

  Stores the `pshared` attribute of `attr` into `*pshared`. On success, returns `0`. On error, returns an error code.

int pthread_rwlockattr_setpshared(pthread_rwlockattr_t *attr, int pshared)

  Sets the `pshared` attribute of `attr` to `pshared`. On success, returns `0`. On error, returns an error code. Note that under Linux, `pshared` must be `PTHREAD_PROCESS_PRIVATE` or pthread_rwlock_init(3) will subsequently fail.

# ERRORS

On error, `errno` is set, either by an underlying function, or as follows:

`EINVAL`

  Arguments are `null` or invalid.

# MT-Level

MT-Safe

# EXAMPLES

A mutex example:

        #include <slack/std.h>
        #include <slack/locker.h>

        int main(int ac, char **av)
        {
            pthread_mutex_t mutex[1];
            Locker *locker;

            errno = pthread_mutex_init(mutex, NULL);
            locker = locker_create_mutex(mutex);

            errno = locker_rdlock(locker);
            errno = locker_unlock(locker);

            errno = locker_wrlock(locker);
            errno = locker_unlock(locker);

            locker_destroy(&locker);
            pthread_mutex_destroy(mutex);

            return EXIT_SUCCESS;
        }

A rwlock example:

        #include <slack/std.h>
        #include <slack/locker.h>

        int main(int ac, char **av)
        {
            pthread_rwlock_t rwlock[1];
            Locker *locker;

            errno = pthread_rwlock_init(rwlock, NULL);
            locker = locker_create_rwlock(rwlock);

            errno = locker_rdlock(locker);
            errno = locker_unlock(locker);

            errno = locker_wrlock(locker);
            errno = locker_unlock(locker);

            locker_destroy(&locker);
            pthread_rwlock_destroy(rwlock);

            return EXIT_SUCCESS;
        }

A debug mutex example:

        #include <slack/std.h>
        #include <slack/locker.h>

        int main(int ac, char **av)
        {
            pthread_mutex_t mutex[1];
            Locker *locker;

            errno = pthread_mutex_init(mutex, NULL);
            locker = locker_create_debug_mutex(mutex);

            errno = locker_rdlock(locker);
            errno = locker_unlock(locker);

            errno = locker_wrlock(locker);
            errno = locker_unlock(locker);

            locker_destroy(&locker);
            pthread_mutex_destroy(mutex);

            return EXIT_SUCCESS;
        }

A debug rwlock example:

        #include <slack/std.h>
        #include <slack/locker.h>

        int main(int ac, char **av)
        {
            pthread_rwlock_t rwlock[1];
            Locker *locker;

            errno = pthread_rwlock_init(rwlock, NULL);
            locker = locker_create_debug_rwlock(rwlock);

            errno = locker_rdlock(locker);
            errno = locker_unlock(locker);

            errno = locker_wrlock(locker);
            errno = locker_unlock(locker);

            locker_destroy(&locker);
            pthread_rwlock_destroy(rwlock);

            return EXIT_SUCCESS;
        }

A non-locking example:

        #include <slack/std.h>
        #include <slack/locker.h>

        int main(int ac, char **av)
        {
            Locker *locker = NULL;

            errno = locker_rdlock(locker);
            errno = locker_unlock(locker);

            errno = locker_wrlock(locker);
            errno = locker_unlock(locker);

            return EXIT_SUCCESS;
        }

# SEE ALSO

libslack(3), `http://raf.org/papers/mt-disciplined.html`

# AUTHOR

20210220 raf <raf@raf.org>