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
nbytessize.int ABT_eventual_wait(ABT_eventual eventual, void **value)Wait for the eventual to be set. Multiple ULTs can wait.
valueis 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.