Trivial C11 threads.h wrapper (public domain)

J

John Tsiombikas

Hello everyone! It's been a long time since I was last on usenet :)

I was anxious to use the new standard C11 threading features, instead of
platform-specific APIs, but unfortunately my system libc (GNU libc) does
not implement that bit that yet.
So, I wrote a trivial C11 thread wrapper over POSIX threads, and
released it in the public domain just in case anyone is itching to use
standard C threading in a new project just like me:
https://github.com/jtsiomb/c11threads

The wrapper is so thin, I didn't think it made sense to make a proper
library out of it, so it's just a header file with "static inline"
functions. Drop it in your project, link with pthread and you're good to
go.

Disclaimer: I used a draft of the C11 standard while writting this code,
since I don't actually have the final document. Feel free to notify me
of any glaring discrepancies or omissions.

Cheers!
 
M

Markus Elfring

So, I wrote a trivial C11 thread wrapper over POSIX threads, and
released it in the public domain just in case anyone is itching to use
standard C threading in a new project just like me:

I have looked at your source file.
https://github.com/jtsiomb/c11threads/blob/1fa0de734e204c1096d0dabac84d0c4497318944/c11threads.h

I suggest to consider the following implementation details a bit more.
1. Would you like to provide a "typedef" for each enumeration?

2. How do you think about to complete error detection and corresponding
exception handling?
2.1 Mapping of error codes from the POSIX thread interface to the values that
are specified by the current standard for the C programming language.
2.2 pthread_mutexattr_init/destroy

Regards,
Markus
 
J

John Tsiombikas

I haven't read it carefully enough to count as proofreading... most of
it seem to make sense, except for a couple of things:
1. pthread_create can return EAGAIN, isn't it almost the same as thrd_nomem?

I though about that for a bit while writting it, but after reading the
pthreads manpage and the C standard a couple of times, I decided it's
probably not the same error, and I thought it would be best to just
leave thrd_nomem out. Might be wrong, I'll go through them again.
2. pthread_mutex_timedlock sucks badly -- even microsoft got it right!
Try the following program and try not to scream when you realize
*why* it's not doing what you'd expect :)

...
const int s = 5;
...
t.tv_nsec = 0;
t.tv_sec = s;
/* puke! */
printf("timed_lock(%d) = %d\n", s, pthread_mutex_timedlock(&m, &t));

*gasp* ... it's absolute time ?! I must admit I never used
timedlocks...

I suppose that makes it easier to ask it to try locking the mutex
until next friday at 3 o'clock or something :)
 
J

John Tsiombikas

I have looked at your source file.
https://github.com/jtsiomb/c11threads/blob/1fa0de734e204c1096d0dabac84d0c4497318944/c11threads.h

I suggest to consider the following implementation details a bit more.
1. Would you like to provide a "typedef" for each enumeration?

Would I *like* to do that? probably not, but it's not a matter of
preference. Unless I'm much mistaken, the C11 standard dictates int
as the type of the second argument of mtx_init (where all the mtx_
enumerations go), and as the return of all functions that need to
indicate success or failure (thrd_ enumerations).
2. How do you think about to complete error detection and corresponding
exception handling?
2.1 Mapping of error codes from the POSIX thread interface to the values that
are specified by the current standard for the C programming language.

I just went through both docs and tried to map pthread error codes to
corresponding C11-mandated error codes. Those that had no counterpart, I
just ignored, or bundled them all together under thrd_error as
appropriate. Do you propose a different strategy?
2.2 pthread_mutexattr_init/destroy

Yes that was the most hairy bit. This mechanism doesn't seem to map
exactly to what C11 wants. For instance the C standard talks about the
possibility of having a mtx_timed | mtx_recursive mutex, but as far as
I can tell with pthreads I can specify either PTHREAD_MUTEX_RECURSIVE or
PTHREAD_MUTEX_TIMED_NP (which is not even standard), but not both.
 
M

Markus Elfring

... probably not, but it's not a matter of preference.
Unless I'm much mistaken, the C11 standard dictates int as the type of
the second argument of mtx_init (where all the mtx_ enumerations go),
and as the return of all functions that need to indicate success
or failure (thrd_ enumerations).

I suggest generally that a name is assigned to an enumeration. It might also be
a nice service if a "typedef" will be provided for each enumeration so that it
can become easier to reuse them as a data type in customised data structures.

I just went through both docs and tried to map pthread error codes to
corresponding C11-mandated error codes. Those that had no counterpart,
I just ignored, or bundled them all together under thrd_error as
appropriate. Do you propose a different strategy?

I guess that this implementation detail shows a software design challenge. I do
not like the ignorance of return values.

Yes that was the most hairy bit. This mechanism doesn't seem to map
exactly to what C11 wants. For instance the C standard talks about the
possibility of having a mtx_timed | mtx_recursive mutex, but as far as
I can tell with pthreads I can specify either PTHREAD_MUTEX_RECURSIVE or
PTHREAD_MUTEX_TIMED_NP (which is not even standard), but not both.

Thanks that you pointed out such a portability issue.

I would like to point out once again that your function call
"pthread_mutexattr_init" shows the case of an unused return value. I would
prefer complete error detection and corresponding exception handling.

Some functions from the C standard API (like "mtx_destroy") have got the return
type "void". But the called Pthreads function provides an error code eventually.
How do you think about the reaction "abort()" in this use case?

Regards,
Markus
 
K

Keith Thompson

Markus Elfring said:
I suggest generally that a name is assigned to an enumeration. It
might also be a nice service if a "typedef" will be provided for each
enumeration so that it can become easier to reuse them as a data type
in customised data structures.
[...]

The C standard doesn't provide typedefs for those enumerations.
An implementation that provided one would be intruding on the
user's namespace. And there's no particular reason for a typedef;
enumeration constants are of type int, not of the enumeration type.
 
J

James Kuyper

I suggest generally that a name is assigned to an enumeration. It might also be
a nice service if a "typedef" will be provided for each enumeration so that it
can become easier to reuse them as a data type in customised data structures.

He's trying to do the best that he can to implement a C99 substitute for
a new feature of C2011. C2011 does NOT specify a typedef name (or even
an enum tag) for the enumeration constants required to be declared in
<threads.h>. An implementation could provide a tag or a typedef or both,
but it can only use a reserved identifier for those purposes. If he used
a reserved identifier for that purpose, it would make his C99 code have
undefined behavior. If he used an identifier that was not reserved, his
substitute would be inappropriately infringing on the user's name space.
Thanks that you pointed out such a portability issue.

I would like to point out once again that your function call
"pthread_mutexattr_init" shows the case of an unused return value. I would
prefer complete error detection and corresponding exception handling.

I agree - mtx_init() should return thrd_error if
pthread_mutexattr_init() returns a non-zero value.
Some functions from the C standard API (like "mtx_destroy") have got the return
type "void". But the called Pthreads function provides an error code eventually.
How do you think about the reaction "abort()" in this use case?

mtx_destroy() has, in itself, well-defined behavior that does not
include calling abort(). However, pthread_mutex_destroy() is only
allowed to fail if "The implementation has detected an attempt to
destroy the object referenced by mutex while it is locked or referenced
(for example, while being used in a pthread_cond_timedwait() or
pthread_cond_wait()) by another thread."

I haven't studied the C2011 threading specification carefully enough to
be certain, but I suspect that if C code using this wrapper library
could set up a situation in which pthread_mutex_destroy() would fail,
that code which does so might itself have undefined behavior, according
to C2011. If so, that would be sufficient to justify having
mtx_destroy() call abort().
 
J

Jens Gustedt

Hello,

Am 27.09.2012 05:38, schrieb John Tsiombikas:
Hello everyone! It's been a long time since I was last on usenet :)

I was anxious to use the new standard C11 threading features, instead of
platform-specific APIs, but unfortunately my system libc (GNU libc) does
not implement that bit that yet.
So, I wrote a trivial C11 thread wrapper over POSIX threads, and
released it in the public domain just in case anyone is itching to use
standard C threading in a new project just like me:
https://github.com/jtsiomb/c11threads

There is a major flaw in your implementation that concerns the type of
the thread functions. You are simply casting C11 function type

int f(void*)

to the POSIX type

void* f(void*)

That is not only undefined behavior what is concerned C, but also
dangerous. The calling conventions for these type of functions may be
different on a given architecture, e.g returning the int value in a
reserved register and the void* on the stack.

Taking care of that incompatibility between C11 an POSIX threads needs
a bit more care than that, I think, if you want to be portable.
The wrapper is so thin, I didn't think it made sense to make a proper
library out of it, so it's just a header file with "static inline"
functions. Drop it in your project, link with pthread and you're good to
go.

Disclaimer: I used a draft of the C11 standard while writting this code,
since I don't actually have the final document. Feel free to notify me
of any glaring discrepancies or omissions.

The xtime things are gone in the final version and all is now done
with the time structures as they existed before.

There is already an implementation of C11 threads as a wrapper around
POSIX threads that is publicly available. It is integrated in P99:

http://p99.gforge.inria.fr/

The C11 compatibility wrapper of P99 should be completely interface
compatible to C11 threads. (the include files are named differently,
though.)

Basically it also is a shallow wrapper using inline functions around
POSIX, but in addition P99 also offers
- an implementation/wrapper for the atomics
- of thread local variables
- _Generic (emulated through a macro)

if they are available (generally through gcc and friends)

Jens
 
K

Kaz Kylheku

*gasp* ... it's absolute time ?! I must admit I never used
timedlocks...

I suppose that makes it easier to ask it to try locking the mutex
until next friday at 3 o'clock or something :)

What it allows you to do is resume an interrupted wait, so that the wakeup
still happens at the originally predetermined time.

With absolutely measured suspensions, you can make a thread wake up, say 10
times a second regardless of the duration of the processing that takes place in
betwen the wakeups (so long as it does not overrun the 0.1 second interval).
In one hour, it will wake up exactly 36000 times.

That kind of timing is sometimes required in real-time programming.
 
E

Eric Sosman

[...]
*gasp* ... it's absolute time ?! I must admit I never used
timedlocks...

I suppose that makes it easier to ask it to try locking the mutex
until next friday at 3 o'clock or something :)

"Or something." It certainly makes it easier to set up a
timeout that will fire every dT milliseconds. Otherwise you'd
have fun computing how much time to wait between "now" [HIGHER-
PRIORITY THREAD RUNS] and [PAGE FAULT] "then."

(Open)VMS had/has a handy feature: Time-related services
accepted "absolute time" or "relative time" arguments, so the
program could specify "for interval dT" or "until time T" for
any kind of timeout. Also, the system's clock-adjusting code
knew which kinds of waits were which, so "wait ten seconds"
would wait for ten seconds regardless of adjustments, while
"wait until 23:17:10" would pay attention to them.
 
G

Giorgos Keramidas

Lol yeah. And the best thing is that it seems that mtx_timedlock waits
"until after the TIME_UTC-based calendar time pointed to by ts" -- they
looked at pthread_mutex_timedlock and thought "nice! much better than
select(), poll(), WaifForSingleObjects(), epoll, kqueue, ..."

Holy crap! This is evil(TM). I think I've used it at least once in the
entirely wrong way thinking it works exactly like nanosleep timeouts!
 
M

Markus Elfring

There is already an implementation of C11 threads as a wrapper around
POSIX threads that is publicly available.

I would interpret source code like the following as an update candidate.

http://p99.gforge.inria.fr/p99-html/p99__threads_8h_source.html :
....
00633 // 7.26.4 Mutex functions
00634
00638 p99_inline
00639 void mtx_destroy(mtx_t *p00_mtx) {
00640 (void)pthread_mutex_destroy(&P99_ENCP(p00_mtx));
00641 }
....


How do think about to get rid of the cast to the return type "void" in such use
cases?

Regards,
Markus
 
K

Keith Thompson

Markus Elfring said:
I would interpret source code like the following as an update candidate.

I'm not sure what you mean by "update candidate".
http://p99.gforge.inria.fr/p99-html/p99__threads_8h_source.html :
...
00633 // 7.26.4 Mutex functions
00634
00638 p99_inline
00639 void mtx_destroy(mtx_t *p00_mtx) {
00640 (void)pthread_mutex_destroy(&P99_ENCP(p00_mtx));
00641 }
...


How do think about to get rid of the cast to the return type "void" in
such use cases?

It doesn't matter much. The POSIX pthread_mutex_destroy() function
returns an int result; the C11 mtx_destroy() function returns void.
The (void) cast in this particular implementation is probably
there to silence a compiler warning about the result of the call
being discarded. The semantics are exactly the same with or without
the cast.

Could you be reading more into this than is actually there?
 
J

Jens Gustedt

Am 30.09.2012 23:28, schrieb Keith Thompson:
I'm not sure what you mean by "update candidate".


It doesn't matter much. The POSIX pthread_mutex_destroy() function
returns an int result; the C11 mtx_destroy() function returns void.
The (void) cast in this particular implementation is probably
there to silence a compiler warning about the result of the call
being discarded. The semantics are exactly the same with or without
the cast.

Exactly, in particular there are some linux systems where the headers
are annotated with gcc extensions that make it difficult to ignore the
return of a system function.

And if you (Markus) refer to the fact of "casting a return type", this
is something completely different from "casting a function pointer to
a function type with a different return type". In one case the
compiler is supposed to convert the actual return type into something
different (with well defined rules) and in the other case you would
trick the compiler to believe that the return type would be of a
certain type (live on the stack, in a certain hardware register).

In the particular case of a cast with "(void)" has a special semantic
in the standard (here C11), namely to ignore the value an to evaluate
the expression just for its side effects:
6.3.2.2 void ... If an expression of any other type is evaluated as
a void expression, its value or designator is discarded. (A void
expression is evaluated for its side effects.)

and then in an example it even explains
If a function call is evaluated as an expression statement for its
side effects only, the discarding of its value may be made explicit by
converting the expression to a void expression by means of a cast

And then, finally, the C11 standard leaves no way to get semantic of a
failed call accros, here. Do you see a way to use the return value in
some way?

Jens
 
J

Jens Gustedt

Am 01.10.2012 00:37, schrieb Jens Gustedt:
Am 30.09.2012 23:28, schrieb Keith Thompson:
And then, finally, the C11 standard leaves no way to get semantic of a
failed call accros, here. Do you see a way to use the return value in
some way?

Ah, thinking of it a bit more, it is actually not "(void)" which is
problematic but the fact that the function may result in errno being
set to a non-zero value. I'll replace by something like

if (pthread_mutex_destroy(&P99_ENCP(p00_mtx))) errno = 0;

Thanks
Jens
 
E

Eric Sosman

[...]
Ah, thinking of it a bit more, it is actually not "(void)" which is
problematic but the fact that the function may result in errno being
set to a non-zero value. I'll replace by something like

if (pthread_mutex_destroy(&P99_ENCP(p00_mtx))) errno = 0;

This would be a very bad thing to do in an implementation
that is supposed to imitate a Standard C library function.

7.5p3: "The value of errno in the initial thread is zero
at program startup [...] but is never set to zero by any
library function. [...]"
 
J

Jens Gustedt

Am 01.10.2012 01:48, schrieb Eric Sosman:
[...]
Ah, thinking of it a bit more, it is actually not "(void)" which is
problematic but the fact that the function may result in errno being
set to a non-zero value. I'll replace by something like

if (pthread_mutex_destroy(&P99_ENCP(p00_mtx))) errno = 0;

This would be a very bad thing to do in an implementation
that is supposed to imitate a Standard C library function.

7.5p3: "The value of errno in the initial thread is zero
at program startup [...] but is never set to zero by any
library function. [...]"

Right. The reason is that I thought that pthread_mutex_destroy could
set errno to some error value. But re-reading the manual page of that
I see that the error code is in the function return. So there is no
need to do that at all.

(Otherwise I would have had to capture errno before the call and to
restore it if the call to pthread_mutex_destroy failed. Expensive.)

Thanks

Jens
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,994
Messages
2,570,223
Members
46,810
Latest member
Kassie0918

Latest Threads

Top