vsync / spinlock / hclhlock.h
Hierarchical CLH Queue Lock.
Groups: Fair locks, Numa-aware
Example:
/* you can use the default value or redefine it to indicate how many \
clusters you need */
#define HCLH_MAX_CLUSTERS 2
#include <vsync/spinlock/hclhlock.h>
#include <pthread.h>
#include <stdio.h>
#include <assert.h>
#define N 10
#define IT 10
#define EXPECTED_VAL (N) * (IT)
vuint32_t g_x = 0;
vuint32_t g_y = 0;
hclh_lock_t g_lock;
hclh_qnode_t g_qnode[N] = {0};
hclh_tnode_t g_tnode[N] = {0};
void *
run(void *args)
{
vsize_t tid = (vsize_t)args;
/* this should be assigned to either numa group or cache group where the
* thread is running */
vuint32_t cluster = tid % HCLH_MAX_CLUSTERS;
hclhlock_init_tnode(&g_tnode[tid], &g_qnode[tid], cluster);
for (vsize_t i = 0; i < IT; i++) {
hclhlock_acquire(&g_lock, &g_tnode[tid]);
g_x++;
g_y++;
assert(g_x == g_y);
hclhlock_release(&g_tnode[tid]);
}
return NULL;
}
int
main(void)
{
pthread_t threads[N];
hclhlock_init(&g_lock);
for (vsize_t i = 0; i < N; i++) {
pthread_create(&threads[i], NULL, run, (void *)i);
}
for (vsize_t i = 0; i < N; i++) {
pthread_join(threads[i], NULL);
}
printf("Final value %u %u\n", g_x, g_y);
assert(g_x == EXPECTED_VAL);
assert(g_x == g_y);
return 0;
}
References:
Maurice Herlihy, Nir Shavit - The Art of Multiprocessor Programming 7.8.2
Macros
| Macro | Description |
|---|---|
| HCLH_MAX_CLUSTERS | configures the number of available clusters in HCLH lock. |
Macro HCLH_MAX_CLUSTERS
configures the number of available clusters in HCLH lock.
Each cluster has a local queue. Each thread must be associated with a cluster value in [0: HCLH_MAX_CLUSTERS -1]
A cluster can be thought of as a NUMA node or cache group. Threads running on cores that belong to the same cache-group or NUMA node are expected to be associated with the same cluster.
Note: by default 32 clusters are defined. Users can redefine it using:
-DHCLH_MAX_CLUSTERS=N, whereNis the number of clusters the user wishes to set.
Functions
| Function | Description |
|---|---|
| hclhlock_init | Initializes the lock. |
| hclhlock_init_tnode | Initializes the given tnode. |
| hclhlock_acquire | Acquires the lock. |
| hclhlock_release | Releases the lock. |
Function hclhlock_init
static void hclhlock_init(hclh_lock_t *hclh_lock)
Initializes the lock.
Parameters:
hclh_lock: address of hclh_lock object to be initialized.
Function hclhlock_init_tnode
static void hclhlock_init_tnode(hclh_tnode_t *tnode, hclh_qnode_t *qnode, vuint32_t cluster)
Initializes the given tnode.
Parameters:
tnode: address of the hclh_tnode_t object associated with the calling thread.qnode: address of the hclh_qnode_t object associated with the calling thread.cluster: the cluster to which the calling thread belongs.
Note: Must be called once before the first call of
hclhlock_acquire.
Note:
qnodemust live as long as the system is running and not be freed. The reason is that on release qnodes get passed to successor threads.
Note:
tnodeshould live as long as the associated thread lives.
Function hclhlock_acquire
static void hclhlock_acquire(hclh_lock_t *lock, hclh_tnode_t *tnode)
Acquires the lock.
Parameters:
lock: address of the lock to be used.tnode: address of the hclh_tnode_t object associated with the calling thread.
Note:
hclhlock_init_tnodemust be called on the giventnode, beforehclhlock_acquireis called for the first time.
Function hclhlock_release
static void hclhlock_release(hclh_tnode_t *tnode)
Releases the lock.
Parameters:
tnode: address of the hclh_tnode_t object associated with the calling thread.