3 ms·
> 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 bei
by jstimpfle 11y ago
> 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.
- deleted 11y ago[deleted]
- kazinator 11y agoReentrant 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.)
- jstimpfle 11y agoYou got that wrong. 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.
- kazinator 11y agoNo 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".
- jstimpfle 11y agoWhy 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. What you talk about are pure functions.
- kazinator 11y agoOnly pure functions are absolutely reentrant. 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).
- jstimpfle 11y ago