Search code examples
c++multithreadingc++11pthreadsreentrancy

Deduce if a program is going to use threads


Thread-safe or thread-compatible code is good. However there are cases in which one could implement things differently (more simply or more efficiently) if one knows that the program will not be using threads.

For example, I once heard that things like std::shared_ptr could use different implementations to optimize the non-threaded case (but I can't find a reference). I think historically std::string in some implementation could use Copy-on-write in non-threaded code.

I am not in favor or against these techniques but I would like to know if that there is a way, (at least a nominal way) to determine at compile time if the code is being compiled with the intention of using threads.

The closest I could get is to realize that threaded code is usually (?) compiled with the -pthreads (not -lpthreads) compiler option. (Not sure if it is a hard requirement or just recommended.)

In turn -pthreads defines some macros, like _REENTRANT or _THREAD_SAFE, at least in gcc and clang. In some some answers in SO, I also read that they are obsolete.

Are these macros the right way to determine if the program is intended to be used with threads? (e.g. threads launched from that same program). Are there other mechanism to detect this at compile time? How confident would the detection method be?


EDIT: since the question can be applied to many contexts apparently, let me give a concrete case:

I am writing a header only library that uses another 3rd party library inside. I would like to know if I should initialize that library to be thread-safe (or at least give a certain level of thread support). If I assume the maximum level of thread support but the user of the library will not be using threads then there will be cost paid for nothing. Since the 3rd library is an implementation detail I though I could make a decision about the level of thread safety requested based on a guess.


EDIT2 (2021): By chance I found this historical (but influential) library Blitz++ which in the documentation says (emphasis mine)

8.1 Blitz++ and thread safety

To enable thread-safety in Blitz++, you need to do one of these things:

  • Compile with gcc -pthread, or CC -mt under Solaris. (These options define_REENTRANT,which tells Blitz++ to generate thread-safe code).
  • Compile with -DBZ_THREADSAFE, or #define BZ_THREADSAFE before including any Blitz++ headers.

In threadsafe mode, Blitz++ array reference counts are safeguarded by a mutex. By default, pthread mutexes are used. If you would prefer a different mutex implementation, add the appropriate BZ_MUTEX macros to <blitz/blitz.h> and send them [email protected] for incorporation. Blitz++ does not do locking for every array element access; this would result in terrible performance. It is the job of the library user to ensure that appropriate synchronization is used.

So it seems that at some point _REENTRANT was used as a clue for the need of multi-threading code. Maybe it is a very old reference to take seriously.


Solution

  • I support the other answer in that thread-safety decision ideally should not be done on whole program basis, rather they should be for specific areas.

    Note that boost::shared_ptr has thread-unsafe version called boost::local_shared_ptr. boost::intrusive_ptr has safe and unsafe counter implementation.

    Some libraries use "null mutex" pattern, that is a mutex, which does nothing on lock / unlock. See boost or Intel TBB null_mutex, or ATL CComFakeCriticalSection. This is specifically to substitute real mutex for threqad-safe code, and a fake one for thread-unsafe.

    Even more, sometimes it may make sense to use the same objects in thread-safe and thread-unsafe way, depending on current phase of execution. There's also atomic_ref which serves the purpose of providing thread-safe access to underlying type, but still letting work with it in thread unsafe.

    I know a good example of runtime switches between thread-safe and thread-unsafe. See HeapCreate with HEAP_NO_SERIALIZE, and HeapAlloc with HEAP_NO_SERIALIZE.

    I know also a questionable example of the same. Delphi recommends calling its BeginThread wrapper instead of CreateThread API function. The wrapper sets a global variable telling that from now on Delphi Memory Manager should be thread-safe. Not sure if this behavior is still in place, but it was there for Delphi 7.


    Fun fact: in Windows 10, there are virtually no single-threaded programs. Before the first statement in main is executed, static DLL dependencies are loaded. Current Windows version makes this DLL loading paralleled where possible by using thread pool. Once program is loaded, thread pool threads are waiting for other tasks that could be issued by using of Windows API calls or std::async. Sure if program by itself will not use threads and TLS, it will not notice, but technically it is multi-threaded from the OS perspective.