P99
|
◆ P99_FUTEX_COMPARE_EXCHANGE
a catch all macro to operate on p99_futex
The other parameters are expressions that may contain arbitrary code that is valid at the point of calling this macro. The evaluation may include the local variable ACT. Here is a pseudo code of what this macro actually does: for (;;) {
register unsigned const ACT = <load value from FUTEX>;
if (EXPECTED) {
if (ACT != (DESIRED)) {
<try to change value of FUTEX to DESIRED>;
if (<not successful>) continue;
}
unsigned wmin = (WAKEMIN);
unsigned wmax = (WAKEMAX);
if (wmin < wmax) wmax = wmin;
<wakeup at least wmin and at most wmax waiters>
} else {
<block and wait until a change is signaled on FUTEX>
}
}
Only that each of the macro parameters is expanded exactly once for the C code. As this is a loop structure the code resulting from that argument expansion of EXPECTED and DESIRED may effectively be evaluated multiple times at run time, in particular when there is congestion on the futex.
After the futex value has been set atomically to the desired value, waiters may be woken up.
Example 1: a semaphore implementationTo see how to use this, let us look into an implementation of a semaphore, starting with a "post" operation.
The operation should increment the value by one, so DESIRED should be
inline
// name of the local variable
val,
// never wait
true,
// increment the value
val + 1u,
// wake up at most one waiter
0u, 1u);
return 0;
}
So effectively, such a post operation is just incrementing the value by one and then it up wakes some waiters. In a real world implementation this would better be done by using p99_futex_add. A semaphore wait operation should block if the value is inline
// name of the local variable
val,
// block if val is 0 and retry
val > 0,
// once there is val, decrement it, if possible
val - 1u,
// never wake up anybody
0u, 0u);
return 0;
}
A semaphore trywait operation should never block, but only decrement the value if it is not inline
int ret;
// name of the local variable
val,
// never wait
true,
// if there is val decrement by one
(val ? 0u : val - 1u),
// capture the error by side effect
(ret = -!val, 0u),
// never wake up anybody
0u, 0u);
if (ret) errno = EAGAIN;
return ret;
}
Example 2: reference countingAnother example of the use of an p99_futex could be a reference counter. Such a counter can e.g be useful to launch a number of threads, and then wait for all the threads to have finished. int launch_my_threads_detached(void *arg) {
p99_futex* fut = arg;
...
my_counter_unlock(fut);
return 0;
}
p99_futex fut;
p99_futex_init(&fut);
my_counter_lock(&fut);
thrd_t id;
thrd_create(&id, launch_my_threads_detached, &fut);
thrd_detach(id);
}
...
my_counter_wait(&fut);
For that scheme to work we just need three "counter" functions. The first just silently piles up references: inline
}
So effectively, such an operation is just incrementing the value by one. In a real world implementation this would better be done by using ::atomic_fetch_add. An unlock operation should decrement the value and, if the value falls to inline
// name of the local variable
val,
// never wait
true,
// decrement by one
val - 1u,
// no enforced wake up
0u,
// wake up all waiters iff the value falls to 0
((val == 1u) ? P99_FUTEX_MAX_WAITERS : 0u));
}
This unsafe version makes no provision for underflow of the counter in case these functions are used erroneously. A safer variant would look like this: inline
P99_FUTEX_COMPARE_EXCHANGE(f, val,
// name of the local variable
val,
// never wait
true,
// decrement by one, but only if wouldn't be underflow
(val ? val - 1u : 0u),
// no enforced wake up
0u,
// wake up all waiters iff the new value is 0
((val <= 1u) ? P99_FUTEX_MAX_WAITERS : 0u));
}
A wait operation should just expect the counter to fall to inline
P99_FUTEX_COMPARE_EXCHANGE(f, val, 0u, 0u, 0u);
// name of the local variable
val,
// wait until the value is 0
!val,
// don't do anything else, no update
0u,
// and no wake up
0u, 0u);
}
Definition at line 563 of file p99_futex.h. Referenced by p99_cm::p99_cm_lock(), p99_count::p99_count_wait(), p99_iterator::p99_iterator_next(), p99_notifier::p99_notifier_block(), p99_rwl::p99_rwl_rdlock(), p99_rwl::p99_rwl_unlock(), and p99_rwl::p99_rwl_wrlock(). |