P99

◆ P99_FUTEX_COMPARE_EXCHANGE

#define P99_FUTEX_COMPARE_EXCHANGE (   FUTEX,
  ACT,
  EXPECTED,
  DESIRED,
  WAKEMIN,
  WAKEMAX 
)
related

a catch all macro to operate on p99_futex

Parameters
FUTEXa pointer that is compatible to p99_futex volatile*
ACTan identifier that can be used in the following parameters. If used there it will correspond to local register variable of type unsigned const holding the actual value of the futex. That variable cannot be modified and its address cannot be taken.

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.

Parameters
EXPECTEDis an expression that is interpreted as a Boolean condition. The calling thread will be blocked until this expression evaluates to true.
Remarks
EXPECTED should never be set to the constant false, since will result in the thread being blocked in the for loop.
Parameters
DESIREDis an expression that is interpreted as an unsigned. Once the futex fulfills the condition EXPECTED the futex is atomically set to that value if necessary.
Warning
EXPECTED and DESIRED may be evaluated several times if the underlying atomic compare exchange operations fails temporarily. So you should not have destructive side effects in these expressions.

After the futex value has been set atomically to the desired value, waiters may be woken up.

Parameters
WAKEMINis an expression that is interpreted as an unsigned that should not exceed P99_FUTEX_MAX_WAITERS. The calling thread will block until it has woken up at least that number of waiters.
Warning
blocking on the WAKEMIN condition can be an active wait and should be avoided whenever possible.
Parameters
WAKEMAXis an expression that is interpreted as an unsigned that should not exceed P99_FUTEX_MAX_WAITERS. WAKEMAX is adjusted to be at least as large as WAKEMIN.

Example 1: a semaphore implementation

To see how to use this, let us look into an implementation of a semaphore, starting with a "post" operation.

  • Such a post operation on a semaphore should not place any condition to perform the post action so EXPECTED should always be true.

The operation should increment the value by one, so DESIRED should be ACT + 1.

  • It should always wake up potential waiters, so WAKEMAX shouldn't be 0.
  • It should not wake up more than one waiters to avoid the waiters racing for one little token that had been placed. So WAKEMAX should be 1.
inline
int my_sem_post(p99_futex volatile* f) {
// 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 0, and then, once the value is positive, decrement that value by 1 and never wake up any other waiters:

inline
void my_sem_wait(p99_futex volatile* f) {
// 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 0, and never wake up any waiters. A trywait operation must be able to return a value that indicates success or failure of the operation. We set the return value by side effect in the WAKEMIN expression, where we know that it is evaluated only once.

inline
int my_sem_trywait(p99_futex volatile* f) {
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 counting

Another 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;
}
for (size_t i = 0; i < large_number; ++i) {
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
void my_counter_lock(p99_futex volatile* f) {
P99_FUTEX_COMPARE_EXCHANGE(f, val, true, val + 1u, 0u, 0u);
}

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 0, wake up all waiters:

inline
void my_counter_unlock_unsafe(p99_futex volatile* f) {
// 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
void my_counter_unlock(p99_futex volatile* f) {
// 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 0 and do not much else.

inline
void my_counter_wait(p99_futex volatile* f) {
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);
}
Remarks
argument 1 must be an identifier

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().

p99_futex
A counter similar to a conditional variable that allows atomic increment and decrement and to wait fo...
Definition: p99_futex.h:163
f
f
Definition: p99_str.h:138
p99_futex::P99_FUTEX_COMPARE_EXCHANGE
#define P99_FUTEX_COMPARE_EXCHANGE(FUTEX, ACT, EXPECTED, DESIRED, WAKEMIN, WAKEMAX)
a catch all macro to operate on p99_futex
Definition: p99_futex.h:563
p99_futex::P99_FUTEX_MAX_WAITERS
#define P99_FUTEX_MAX_WAITERS
the maximum number of waiters that an p99_futex may have
Definition: p99_futex.h:178
i
P00_CLAUSE2 i(_Pragma("weak p00_getopt_comp"))(_Pragma("weak p00_getopt_comp
p99_futex::p99_futex_init
p99_futex * p99_futex_init(p99_futex *p00_c, unsigned p00_ini)
Initialize an p99_futex object.
errno
errno
Definition: p99_constraint.h:199