Libraries that are not designed from the ground up to be thread safe almost certainly aren't so that's the safe assumption. (And even plenty of libraries that are designed to be thread safe aren't because it is hard to do this right.)
> plenty of libraries that are designed to be thread safe aren't because it is hard to do this right.
Is it? Are we talking about fully thread-safe or only being reentrant?
For the most part, being reentrant is sufficient -- i think there are few cases where it makes sense to be really thread-safe (i.e., lock mutexes on every access as opposed to let the client do the locking as needed) since that forbids composability.
Making a function reentrant is mostly very easy. It's not trivial to assert that external dependencies are also reentrant, but most libraries don't have external dependencies.
Reentrant is more general than thread-safe. Thread-safe can be achieved with hacks specific to a threading model: thread-specific variables, locks and so on.
Reentrant is just about absolute. As in, pretty much, "you can stick this routine into an interrupt context on a randomly selected microcontroller and everything is cool".
Look at this particular zlist example. We can make it thread-safe with thread-specific variable for the iteration cursor. Yet, the function will not still support recursion, or even nested iteration of the list. A reentrant function will.
Making a function reentrant is not easy, if it has arguments which are pointers to various objects.
Only functions that receive everything by value and don't refer to anything global are easily made reentrant.
(Of course, the compiled code for all functions refers to global things: registers, such as the stack pointer. So reetrancy isn't absolute; it depends on machine contexts being correctly saved and restored by all the responsible code, like trap handler entry points. The compiler also can't do stupid things like use static buffers for passing structures (very early C compilers did that) so the source code only looks reentrant.)
For example, a fully thread-safe binary tree would be a tree data structure which allows concurrent insert operations on the same tree instance.
Whereas a reentrant binary tree allows concurrent inserts, but not on the same tree instance.
So, your typical reentrant function takes care not to depend on or modify global state. But it might read from and/or write to an implementation-specific datastructure.
Run a "zgrep -l reentrant /usr/share/man/man3/*.gz" which will convince you that's the usual notion. In particular, look at the manpage of qsort / qsort_r.
You can see that making a function reentrant means typically converting static data dependencies into dynamic ones. The straightforward and very often simple approach is using function arguments (plus a contract that the arguments are not shared between "threads").
Making dependencies thread-local is another approach, and "errno" is a good example for that.
No kidding! For instance, the memcpy function is reentrant: but only as long as two contexts don't call it at the same time, and pass pointers to the same memory. It is not unconditionally reentrant. Some functions are; those that don't take pointers to anything as arguments. I covered that in my comment.
An absolutely reentrant function could use memcpy internally (for instance to copy one object stack on its stack to another), so in other words, memcpy doesn't have bad properties that prevent it from being integrated into fully reentrant situation.
An insert function that doesn't handle two threads inserting into the same tree isn't reentrant when called that way. It execution cannot be suspended and re-entered, except with different objects.
The posix _r doesn't mean "absolutely reentrant"; it just means "this function doesn't do stupid things under the hood with globals that would prevent it from being used in reentrant code".
Why do you insist? Please argue with the POSIX people writing these manpages (and correct Wikipedia) if you think that they are all wrong and your nomenclature is the right one.
Reentrancy is logically weaker than thread safety.
For instance, malloc is thread safe in POSIX, right?
Yet, you cannot call it from a signal handler, at least not an asynchronous one. This is because it isn't reentrant.
(POSIX would call it "async signal safe", but that's totally POSIX-specific terminology. POSIX uses some terms in POSIX specific ways, unsurprisingly. And "async signal safe" is something less than reentrant because functions can be made async signal safe by manipulating signal masks to avoid being re-entered.)
I'm not the one who made this discussion about POSIX; my original comment wasn't about how POSIX uses "reentrant" (and I don't care, for that matter).
> Reentrancy is logically weaker than thread safety.
It isn't; neither is an implication of the other. If you mistrust manpages, lookup the definition on Wikipedia.
But in many contexts "reentrant" is the better "thread-safe", as in "what you actually want" (give up mandatory locks to get scalability, composability...).
Do you have an example of a function inherently reentrant (pure function), or a function or object used in a reentrant manner, that isn't also thread-safe?
Wikipedia's first paragraph on Reentrancy completely agrees with me; I'm not inclined to change a word.
POSIX doesn't define "reentrancy" or "reentrant", though it uses those terms. They are not defined in the documents it includes by normative reference. ISO C is one of those references and also throws the word around. ISO C refers to a document called ISO 2382, whose Part 1 gives a technical vocabulary; it might be defined there.
By POSIX, of course I don't mean the man pages on a Linux system, but the actual specification. I refer the online version from time to time, specifically this one: http://pubs.opengroup.org/onlinepubs/9699919799/