using C11's _Atomic()
[email protected] (Dave Mitchell) Sun, 3 May 2026 17:49:40 +0100
| Newsgroups | perl.perl5.porters |
|---|---|
| Message-ID | <[email protected]> |
I wish to make use of C11's _Atomic() facility - where available - to fix
some threads race conditions. I'll explain further down what I'm trying to
fix, but first I want to ask whether allowing the perl interpreter to use
_Atomic() is sensible, and whether my plan to make it optionally available
is sensible.
C11 allows you to write code like this:
#include <stdatomic.h>
_Atomic(int) count;
void
run_thread() {
...
count++;
...
}
where if multiple threads are running run_thread() simultaneously, count
won't lose any iterations.
'_Atomic(int)' is a type which can hold an integer, and may, depending on
the platform and compiler, also contain other extra info (e.g. a lock).
So you can't typecast between int foo[] and _Atomic(int) bar[[], since
they may not be the same size. But you can treat each element as if it's
an int.
Such atomic variables cause the compiler to plant suitable machine code so
that things like count++ and count+=10 are atomic, using special
instructions and/or adding memory barriers etc. The intention is that such
things are typically very lightweight and lock-free (but might fall back
to more heavyweight things like locks on platforms where lock-free isn't
possible).
Now, perl core isn't on C11 yet, but I've found that on GCC 14.2 at least,
just chucking in '#include <stdatomic.h>' allows code to be written using
_Atomic(), and I've confirmed that such code does indeed start acting
atomically.
So I'm envisaging doing the following. I'll add a probe to Configure which
tries to compile and run a small C program like:
#include <stdatomic.h>
_Atomic(int) count = 0;
int
main() {
count++;
count += 10;
return count == 11 ? 0 : 1;
}
which sets i_stdatomic say. Then in perl.h have:
#ifdef I_STDATOMIC
# include <stdatomic.h>
# define PERL_ATOMIC(atype) _Atomic(atype)
#else
# define PERL_ATOMIC(atype) atype
#endif
Then have entries in intrpvar.h like
PERLVARI(I, sig_pending, PERL_ATOMIC(int), 0)
Does this sound like a plausible approach - i.e. if the compiler seems to
support it, use it? and if so, is the above the correct way to probe for
it and utilise it?
The specific issue I'm trying to address is thread pseudo-signals. The
signal infrastructure basically sets PL_sig_pending (and some other stuff)
in the signal handler, then the perl interpreter, after each op, does
PERL_ASYNC_CHECK(); which expands to
if (UNLIKELY(PL_sig_pending)) PL_signalhook(aTHX)
This works ok because the signal handler and the interpreter can't be
running simultaneously. But the threads module allows you to call:
$thread->kill('HUP');
which emulates sending a signal to $thread by setting $thread's
PL_sig_pending and similar variables. This isn't thread-safe: it locks
the threads mutex before sending so that only one thread at a time can
send a pseudo-signal to a particular thread, but the thread itself isn't
locking a mutex each time it tests PL_sig_pending in PERL_ASYNC_CHECK(),
nor when resetting it to false.
It would be cost-prohibitive to lock a mutex each time the run loop tests
PL_sig_pending, and insane to lock it within an OS signal handler.
Atomic types fix all these issues.