CK_COHORT(3)

✨ ck_cohort ✨

okay bestie, welcome to ck_cohort β€” the generalized interface for lock cohorts πŸ’… this one lives in the Concurrency Kit, aka libck, and you link it with -lck. no cap, it's giving very "my threads need to stop fighting over the same lock" energy 🧚


🌸 LIBRARY 🌸

Concurrency Kit β€” <pink>libck</pink>, link it with <hotpink>-lck</hotpink> πŸŽ€


🌈 SYNOPSIS 🌈

#include <ck_cohort.h>

CK_COHORT_PROTOTYPE(COHORT_NAME cohort_name,
    LOCK_FXN global_lock_method, LOCK_FXN global_unlock_method,
    LOCK_FXN local_lock_method, LOCK_FXN local_unlock_method);

CK_COHORT_TRYLOCK_PROTOTYPE(COHORT_NAME cohort_name,
    LOCK_FXN global_lock_method, LOCK_FXN global_unlock_method,
    BOOL_LOCK_FXN global_locked_method,
    BOOL_LOCK_FXN global_trylock_method, LOCK_FXN local_lock_method,
    LOCK_FXN local_unlock_method, BOOL_LOCK_FXN local_locked_method,
    BOOL_LOCK_FXN local_trylock_method);

CK_COHORT_INSTANCE(COHORT_NAME cohort_name);

CK_COHORT_INIT(COHORT_NAME cohort_name, ck_cohort *cohort,
    void *global_lock, void *local_lock, unsigned int pass_limit);

CK_COHORT_LOCK(COHORT_NAME cohort_name, ck_cohort *cohort,
    void *global_context, void *local_context);

CK_COHORT_UNLOCK(COHORT_NAME cohort_name, ck_cohort *cohort,
    void *global_context, void *local_context);

so like, quick translation bestie 🫢 β€” <purple>LOCK_FXN</purple> means a method with the signature

void(void *lock, void *context)

and <purple>BOOL_LOCK_FXN</purple> means a method with the signature

bool(void *lock, void *context)

the context argument in each of those signatures is literally there so you can pass along any extra info your lock might need for its lock, unlock and trylock methods. and the values for that argument get handed to every single call of CK_COHORT_LOCK(3), CK_COHORT_UNLOCK(3), CK_COHORT_LOCKED(3), and CK_COHORT_TRYLOCK(3) ✨ iykyk.


πŸ’– DESCRIPTION πŸ’–

ck_cohort.h gives you an interface for defining lock cohorts with literally any lock type you want πŸ‘‘ and cohorts?? cohorts are the mechanism for coordinating threads on NUMA architectures so that the lock gets passed between threads on different clusters way less often. it's giving "stop bouncing my cache lines around" fr fr πŸ¦‹

before you can use a cohort, you have to define a cohort type using either the CK_COHORT_PROTOTYPE() macro or the CK_COHORT_TRYLOCK_PROTOTYPE() macro. these macros are where you tell it which lock methods you want your cohort to use πŸ’… if you want the deets, go read the CK_COHORT_PROTOTYPE(3) and CK_COHORT_TRYLOCK_PROTOTYPE(3) man pages, they're right there.


πŸŽ€ EXAMPLE πŸŽ€

okay this is the part where it gets iconic, look at this whole thing:

#include <stdlib.h>
#include <pthread.h>

#include <ck_pr.h>
#include <ck_cohort.h>
#include <ck_spinlock.h>

/*
 * Create cohort methods with signatures that match
 * the required signature
 */
static void
ck_spinlock_lock_with_context(ck_spinlock_t *lock, void *context)
{
        (void)context;
        ck_spinlock_lock(lock);
        return;
}

static void
ck_spinlock_unlock_with_context(ck_spinlock_t *lock, void *context)
{
        (void)context;
        ck_spinlock_unlock(lock);
        return;
}

static bool
ck_spinlock_locked_with_context(ck_spinlock_t *lock, void *context)
{
        (void)context;
        return ck_spinlock_locked(lock);
}

/*
 * define a cohort type named "test_cohort" that will use
 * the above methods for both its global and local locks
 */
CK_COHORT_PROTOTYPE(test_cohort,
        ck_spinlock_lock_with_context, ck_spinlock_unlock_with_context, ck_spinlock_locked_with_context,
        ck_spinlock_lock_with_context, ck_spinlock_unlock_with_context, ck_spinlock_locked_with_context)

static ck_spinlock_t global_lock = CK_SPINLOCK_INITIALIZER;
static unsigned int ready;

static void *
function(void *context)
{
        CK_COHORT_INSTANCE(test_cohort) *cohort = context;

        while (ready == 0);

        while (ready > 0) {
                /*
                 * acquire the cohort lock before performing critical section.
                 * note that we pass NULL for both the global and local context
                 * arguments because neither the lock nor unlock functions
                 * will use them.
                 */
                CK_COHORT_LOCK(test_cohort, cohort, NULL, NULL);

                /* perform critical section */

                /* relinquish cohort lock */
                CK_COHORT_UNLOCK(test_cohort, cohort, NULL, NULL);
        }

        return NULL;
}

int
main(void)
{
        unsigned int nthr = 4;
        unsigned int n_cohorts = 2;
        unsigned int i;

        /* allocate 2 cohorts of the defined type */
        CK_COHORT_INSTANCE(test_cohort) *cohorts =
            calloc(n_cohorts, sizeof(CK_COHORT_INSTANCE(test_cohort)));

        /* create local locks to use with each cohort */
        ck_spinlock_t *local_locks =
                calloc(n_cohorts, sizeof(ck_spinlock_t));

        pthread_t *threads =
                calloc(nthr, sizeof(pthread_t));

        /* initialize each of the cohorts before using them */
        for (i = 0 ; i < n_cohorts ; ++i) {
                CK_COHORT_INIT(test_cohort, cohorts + i, &global_lock, local_locks + i,
                        CK_COHORT_DEFAULT_LOCAL_PASS_LIMIT);
        }

        /* start each thread and assign cohorts equally */
        for (i = 0 ; i < nthr ; ++i) {
                pthread_create(threads + i, NULL, function, cohorts + (i % n_cohorts));
        }

        ck_pr_store_uint(&ready, 1);
        sleep(10);
        ck_pr_store_uint(&ready, 0);

        for (i = 0 ; i < nthr ; ++i) {
                pthread_join(threads[i], NULL);
        }

        return 0;
}

ate, honestly πŸ”₯


πŸ‘‘ SEE ALSO πŸ‘‘

and if you want even more info, it's all available at http://concurrencykit.org/ πŸ’œ


okay that's the whole thing, bestie β€” this page is from <pink>February 24, 2013</pink>, it's the ck_cohort(3) page from Concurrency Kit, and yeah. iconic. periodt. πŸ’…βœ¨πŸŽ€

πŸ“¦ where this page comes from