Eventuals and Reader-Writer Locks

This tutorial covers two specialized synchronization primitives: eventuals for single-write broadcast scenarios, and reader-writer locks for read-heavy workloads.

Eventuals

Eventuals are simpler than a combination of mutex and condition variable for scenarios where one of more consumers wait on a value from a single producer.

 1/*
 2 * Eventual example: Single-value synchronization
 3 * Simpler than futures for single-write scenarios
 4 */
 5
 6#include <stdio.h>
 7#include <abt.h>
 8
 9#define NUM_WAITERS 4
10
11typedef struct {
12    int waiter_id;
13    ABT_eventual eventual;
14} waiter_arg_t;
15
16void writer_thread(void *arg)
17{
18    ABT_eventual eventual = (ABT_eventual)arg;
19    int result = 42;
20
21    printf("Writer: computing result...\n");
22    /* Simulate computation */
23    for (int i = 0; i < 1000000; i++);
24
25    printf("Writer: setting eventual with value %d\n", result);
26    ABT_eventual_set(eventual, &result, sizeof(int));
27}
28
29void waiter_thread(void *arg)
30{
31    waiter_arg_t *waiter = (waiter_arg_t *)arg;
32
33    printf("  Waiter %d: waiting for result...\n", waiter->waiter_id);
34
35    int *result;
36    ABT_eventual_wait(waiter->eventual, (void **)&result);
37
38    printf("  Waiter %d: got result = %d\n", waiter->waiter_id, *result);
39}
40
41int main(int argc, char **argv)
42{
43    ABT_xstream xstream;
44    ABT_pool pool;
45    ABT_thread writer;
46    ABT_thread waiters[NUM_WAITERS];
47    ABT_eventual eventual;
48    waiter_arg_t waiter_args[NUM_WAITERS];
49
50    ABT_init(argc, argv);
51
52    printf("=== Eventual Example ===\n");
53    printf("One writer, multiple waiters\n\n");
54
55    ABT_xstream_self(&xstream);
56    ABT_xstream_get_main_pools(xstream, 1, &pool);
57
58    /* Create eventual which will contain an int */
59    ABT_eventual_create(sizeof(int), &eventual);
60
61    /* Create waiters first */
62    for (int i = 0; i < NUM_WAITERS; i++) {
63        waiter_args[i].waiter_id = i;
64        waiter_args[i].eventual = eventual;
65        ABT_thread_create(pool, waiter_thread, &waiter_args[i],
66                          ABT_THREAD_ATTR_NULL, &waiters[i]);
67    }
68
69    /* Create writer */
70    ABT_thread_create(pool, writer_thread, eventual,
71                      ABT_THREAD_ATTR_NULL, &writer);
72
73    /* Wait for all */
74    ABT_thread_free(&writer);
75    for (int i = 0; i < NUM_WAITERS; i++) {
76        ABT_thread_free(&waiters[i]);
77    }
78
79    ABT_eventual_free(&eventual);
80
81    printf("\nEventual: single writer, multiple readers pattern\n");
82
83    ABT_finalize();
84    return 0;
85}

Key Properties: - ABT_eventual_wait() can be called by multiple ULTs - All waiting ULTs are unblocked when ABT_eventual_set() is called - The value remains available for subsequent waits - Simpler than futures (explained later) when you don’t need compartments

Note

If the eventual is used only as a synchronization mechanism with no attached value, a static version of it (ABT_eventual_memory) may be used, in a way similar to ABT_mutex_memory.

Reader-Writer Locks

Reader-writer locks allow multiple concurrent readers but exclusive writers:

 1/*
 2 * Reader-writer lock example
 3 * Multiple concurrent readers, exclusive writer
 4 */
 5
 6#include <stdio.h>
 7#include <abt.h>
 8
 9#define NUM_READERS 6
10#define NUM_WRITERS 2
11
12typedef struct {
13    int value;
14    ABT_rwlock rwlock;
15} shared_data_t;
16
17typedef struct {
18    int worker_id;
19    shared_data_t *shared;
20} worker_arg_t;
21
22void reader_thread(void *arg)
23{
24    worker_arg_t *worker = (worker_arg_t *)arg;
25
26    ABT_rwlock_rdlock(worker->shared->rwlock);
27    printf("Reader %d: reading value = %d\n",
28           worker->worker_id, worker->shared->value);
29    ABT_rwlock_unlock(worker->shared->rwlock);
30}
31
32void writer_thread(void *arg)
33{
34    worker_arg_t *worker = (worker_arg_t *)arg;
35
36    ABT_rwlock_wrlock(worker->shared->rwlock);
37    worker->shared->value++;
38    printf("  Writer %d: wrote value = %d\n",
39           worker->worker_id, worker->shared->value);
40    ABT_rwlock_unlock(worker->shared->rwlock);
41}
42
43int main(int argc, char **argv)
44{
45    ABT_xstream xstream;
46    ABT_pool pool;
47    ABT_thread readers[NUM_READERS];
48    ABT_thread writers[NUM_WRITERS];
49    worker_arg_t reader_args[NUM_READERS];
50    worker_arg_t writer_args[NUM_WRITERS];
51    shared_data_t shared;
52
53    ABT_init(argc, argv);
54
55    printf("=== Reader-Writer Lock Example ===\n");
56    printf("Multiple concurrent readers, exclusive writers\n\n");
57
58    shared.value = 0;
59    ABT_rwlock_create(&shared.rwlock);
60
61    ABT_xstream_self(&xstream);
62    ABT_xstream_get_main_pools(xstream, 1, &pool);
63
64    /* Create readers */
65    for (int i = 0; i < NUM_READERS; i++) {
66        reader_args[i].worker_id = i;
67        reader_args[i].shared = &shared;
68        ABT_thread_create(pool, reader_thread, &reader_args[i],
69                          ABT_THREAD_ATTR_NULL, &readers[i]);
70    }
71
72    /* Create writers */
73    for (int i = 0; i < NUM_WRITERS; i++) {
74        writer_args[i].worker_id = i;
75        writer_args[i].shared = &shared;
76        ABT_thread_create(pool, writer_thread, &writer_args[i],
77                          ABT_THREAD_ATTR_NULL, &writers[i]);
78    }
79
80    /* Wait for all */
81    for (int i = 0; i < NUM_READERS; i++) {
82        ABT_thread_free(&readers[i]);
83    }
84    for (int i = 0; i < NUM_WRITERS; i++) {
85        ABT_thread_free(&writers[i]);
86    }
87
88    ABT_rwlock_free(&shared.rwlock);
89
90    printf("\nRWLock: readers can run concurrently, writers are exclusive\n");
91
92    ABT_finalize();
93    return 0;
94}

Key Points: - Multiple readers can hold read lock simultaneously - Writers get exclusive access (no readers or other writers) - Ideal for read-heavy workloads

Use Cases: - Configuration data (frequent reads, rare updates) - Lookup tables and caches - Shared metadata - Directory structures

Performance: Reader-writer locks have overhead. Use only when: - Reads vastly outnumber writes - Critical sections are long enough to amortize locking overhead - Otherwise, use regular mutexes

Common Pitfalls

Using Eventuals for Multiple Writers
/* WRONG: Multiple threads calling ABT_eventual_set() */
void worker(void *arg) {
    ABT_eventual_set(eventual, &value);  /* Race condition! */
}

Eventuals support only one set() call. For multiple producers, use futures.

Using RWLocks for Write-Heavy Workloads

If writes are common, regular mutexes are usually faster.

Forgetting to Free Resources
ABT_eventual_create(sizeof(int), &eventual);
/* ... use it ... */
ABT_eventual_free(&eventual);  /* Don't forget! */

ABT_rwlock_create(&rwlock);
/* ... use it ... */
ABT_rwlock_free(&rwlock);  /* Don't forget! */

API Reference

Eventual Functions:
  • int ABT_eventual_create(int nbytes, ABT_eventual *neweventual)

    Create an eventual for a value of nbytes size.

  • int ABT_eventual_wait(ABT_eventual eventual, void **value)

    Wait for the eventual to be set. Multiple ULTs can wait. value is set to point to the eventual’s stored value.

  • int ABT_eventual_test(ABT_eventual eventual, void **value, int *is_ready)

    Non-blocking test if eventual is ready.

  • int ABT_eventual_set(ABT_eventual eventual, void *value, int nbytes)

    Set the eventual’s value, unblocking all waiters. Can only be called once per eventual.

  • int ABT_eventual_free(ABT_eventual *eventual)

    Free an eventual object.

Reader-Writer Lock Functions:
  • int ABT_rwlock_create(ABT_rwlock *newrwlock)

    Create a reader-writer lock.

  • int ABT_rwlock_rdlock(ABT_rwlock rwlock)

    Acquire read lock. Multiple readers can hold this simultaneously.

  • int ABT_rwlock_wrlock(ABT_rwlock rwlock)

    Acquire write lock. Exclusive access (blocks all readers and writers).

  • int ABT_rwlock_unlock(ABT_rwlock rwlock)

    Release read or write lock.

  • int ABT_rwlock_free(ABT_rwlock *rwlock)

    Free a reader-writer lock.