vsync / map / listset_coarse.h
This implementation of listset uses one lock to serialize all operations.
Groups: Linearizable, SMR-required
Example:
#include <vsync/map/listset_coarse.h>
#include <vsync/smr/gdump.h>
#include <pthread.h>
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#define N 11
typedef struct data_s {
vlistset_node_t lst_node; /* embed list set node */
vlistset_key_t key;
smr_node_t smr_node; /* embed smr node */
} data_t;
#define MIN_KEY 0
#define MAX_KEY 10
gdump_t g_gdump;
vlistset_t g_lst;
pthread_rwlock_t g_lock;
static inline void
thread_rw_read_acq(void *arg)
{
pthread_rwlock_t *lock = (pthread_rwlock_t *)arg;
int ret = pthread_rwlock_rdlock(lock);
assert(ret == 0);
}
static inline void
thread_rw_read_rel(void *arg)
{
pthread_rwlock_t *lock = (pthread_rwlock_t *)arg;
int ret = pthread_rwlock_unlock(lock);
assert(ret == 0);
}
static inline void
thread_rw_write_acq(void *arg)
{
pthread_rwlock_t *lock = (pthread_rwlock_t *)arg;
int ret = pthread_rwlock_wrlock(lock);
assert(ret == 0);
}
static inline void
thread_rw_write_rel(void *arg)
{
pthread_rwlock_t *lock = (pthread_rwlock_t *)arg;
int ret = pthread_rwlock_unlock(lock);
assert(ret == 0);
}
smr_rwlock_lib_t g_rwlock_lib = {thread_rw_read_acq, thread_rw_read_rel,
thread_rw_write_acq, thread_rw_write_rel,
&g_lock};
void
free_cb(smr_node_t *snode, void *args)
{
data_t *data = V_CONTAINER_OF(snode, data_t, smr_node);
free(data);
(void)args;
}
int
cmp_cb(vlistset_node_t *node, vlistset_key_t key)
{
data_t *data = (data_t *)node;
if (data->key == key) {
return 0;
} else if (data->key < key) {
return -1;
} else {
return 1;
}
}
void
retire_cb(vlistset_node_t *node, void *args)
{
data_t *data = (data_t *)node;
gdump_retire(&g_gdump, &data->smr_node, free_cb, NULL);
(void)args;
}
int
yield_cb(void *args)
{
(void)args;
return sched_yield();
}
vatomic8_t g_stop = VATOMIC_INIT(0);
void
reclaim(void)
{
while (vatomic8_read(&g_stop) == 0) {
vsize_t count = gdump_recycle(&g_gdump, yield_cb, NULL, 1);
if (count > 0) {
printf("%zu node(s) were reclaimed\n", count);
}
}
}
void
reader(gdump_thread_t *thread)
{
data_t *data = NULL;
gdump_register(&g_gdump, thread);
for (vlistset_key_t key = MIN_KEY; key <= MAX_KEY; key++) {
gdump_enter(&g_gdump, thread);
data = (data_t *)vlistset_get(&g_lst, key);
if (data) {
assert(key == data->key);
printf("Reader found key %lu\n", data->key);
}
gdump_exit(&g_gdump, thread);
}
gdump_deregister(&g_gdump, thread);
}
void
writer(gdump_thread_t *thread)
{
data_t *data = NULL;
vbool_t success = false;
gdump_register(&g_gdump, thread);
for (vlistset_key_t key = MIN_KEY; key <= MAX_KEY; key++) {
gdump_enter(&g_gdump, thread);
success = vlistset_remove(&g_lst, key);
gdump_exit(&g_gdump, thread);
if (success) {
printf("Removed node with key %lu\n", key);
} else {
data = malloc(sizeof(data_t));
data->key = key;
gdump_enter(&g_gdump, thread);
success = vlistset_add(&g_lst, key, &data->lst_node);
gdump_exit(&g_gdump, thread);
if (success) {
printf("Added node with key %lu\n", key);
} else {
free(data);
}
}
}
gdump_deregister(&g_gdump, thread);
// writer is done, it can trigger recycle if it wishes
vsize_t count = gdump_recycle(&g_gdump, yield_cb, NULL, 1);
printf("writer reclaimed %zu nodes\n", count);
}
void *
run(void *args)
{
gdump_thread_t thread;
vsize_t tid = (vsize_t)args;
if (tid == 0) {
reclaim();
} else if (tid % 2 == 0) {
writer(&thread);
} else {
reader(&thread);
}
return NULL;
}
int
main(void)
{
pthread_t threads[N];
int ret = pthread_rwlock_init(&g_lock, NULL);
assert(ret == 0);
gdump_init(&g_gdump, g_rwlock_lib);
vlistset_init(&g_lst, retire_cb, NULL, cmp_cb);
for (vsize_t i = 0; i < N; i++) {
pthread_create(&threads[i], NULL, run, (void *)i);
}
for (vsize_t i = 1; i < N; i++) {
pthread_join(threads[i], NULL);
}
vatomic8_write(&g_stop, 1);
pthread_join(threads[0], NULL);
vlistset_destroy(&g_lst);
gdump_destroy(&g_gdump);
ret = pthread_rwlock_destroy(&g_lock);
assert(ret == 0);
return 0;
}
Note: SMR usage is required only in the scenario where users access the node returned by
vlistset_get, which happens without lock protection. The node might then get deleted by a parallelvlistset_removeoperation. If the user does not wish to access the node itself, and usesvlistset_getonly to check if a node exists, then SMR is not necessary as long as no dereferencing of the returned address occurs.
References:
Maurice Herlihy, Nir Shavit - The Art of Multiprocessor Programming 9.4
Functions
| Function | Description |
|---|---|
| DEF_ABSTRACT_LOCK | wraps the lock in use |
| vlistset_init | Initializes the given vlistset_t object lst. |
| vlistset_destroy | Destroys all the remaining nodes in the listset. |
| vlistset_add | Inserts the given node into the listset. |
| vlistset_remove | Removes the node associated with the given key from the listset. |
| vlistset_get | Looks for the listset node associated with the given key. |
Function DEF_ABSTRACT_LOCK
DEF_ABSTRACT_LOCK(vlistset_lock, ttaslock_t, ttaslock_init, ttaslock_acquire, ttaslock_release, ttaslock_tryacquire) typedef struct vlistset_node_s
wraps the lock in use
Function vlistset_init
static void vlistset_init(vlistset_t *lst, vlistset_handle_node_t retire_fun, void *retire_fun_arg, vlistset_cmp_key_t cmp_fun)
Initializes the given vlistset_t object lst.
Note: must be called before threads access the listset.
Parameters:
lst: address of vlistset_t object.retire_fun: address of the function used to retire removed nodes.retire_fun_arg: optional second parameter to pass toretire_fun.cmp_fun: address of the function used for comparing a key invlistset_node_tobject with a given key.
Function vlistset_destroy
static void vlistset_destroy(vlistset_t *lst)
Destroys all the remaining nodes in the listset.
Note: call only after thread join, or after all threads finished accessing the given vlistset_t object.
Parameters:
lst: address of vlistset_t object.
Function vlistset_add
static vbool_t vlistset_add(vlistset_t *lst, vlistset_key_t key, vlistset_node_t *node)
Inserts the given node into the listset.
The node is only inserted if there is no other node associated with the given key exists in the listset.
Note: if the operation fails, users can safely free or reuse the given node. Precondition: the key must be set in the given
nodebefore calling add.
Parameters:
lst: address of vlistset_t object.key: the key value that is used to identify the node.node: address of vlistset_node_t object.
Returns: true operation succeeded, the given node has been added.
Returns: false operation failed, the given node has not been added, because there exists another node associated with the given key.
Note: must be called inside SMR critical section.
Function vlistset_remove
static vbool_t vlistset_remove(vlistset_t *lst, vlistset_key_t key)
Removes the node associated with the given key from the listset.
Note: the registered retire callback will be called on the removed node.
Parameters:
lst: address of vlistset_t object.key: the key identifying the node to remove.
Returns: true the node associated with the given key was removed successfully.
Returns: false could not find a node holding the given key to remove.
Note: must be called inside SMR critical section.
Function vlistset_get
static vlistset_node_t* vlistset_get(vlistset_t *lst, vlistset_key_t key)
Looks for the listset node associated with the given key.
Parameters:
lst: address of vlistset_t object.key: key value.
Returns: vlistset_node_t* address of the listset node associated with the given key.
Returns: NULL if no node associated with the given key was found.
Note: must be called inside SMR critical section.