2

I'm creating a C library on Linux which has several functions, which together operate upon some global data. In order for these functions to be thread safe, they must employ mutexes at the appropriate points in the code.

In Linux, in order to use pthreads in an application, one needs to link in the appropriate library, -lpthread. In the case of my library once compiled, I'd like to make it work both if the user of it decided to use pthreads in their application, as well as if they don't.

In the case where a developer does not use threads in their application, they will not link against pthreads. Therefore I'd like my compiled library to not require it, and furthermore, employing mutexes in a single threaded application uses needless overhead (not to mention is silly).

Is there some kind of way to write code (with GCC extensions if necessary) that a certain block of code will only run if certain symbols were linked in? I'm aware I can use dlopen() and friends, but that in itself would require some of what I'm trying to avoid. I imagine what I'm looking for must exist, as several standard functions are in the same boat, and would require mutexes to be thread safe (and they are), but work even when not linked with pthreads.

On this point, I notice that FreeBSD's popen() function on line 66 & 67 employs a non portable check - isthreaded, to determine if threads are used or not, and whether to use mutexes. I doubt anything like that is standardized in any way. But more to the point such code can't compile and link if the symbols aren't recognized, which in Linux, the mutex symbols won't even be present if pthread is not linked.

To summarize: On Linux, how does one create a library, which knows when threads are also used, and if so, employs mutexes where appropriate, and does not require linking against pthreads, unless the application developer specifically wants to use threading somewhere?

Joe
  • 79
  • 1
  • 9
  • "In the case where a developer does not use threads in their application, they will not link against pthreads." - but your library *will*. Or if it is static library it will harbor a ton of external pthread references that must be resolved by the calling program, even if they don't use pthreads. I don't think you're going to get what you want without a pair of pthread/no-pthread shared object modules, the correct one dyna-loaded at runtime *by you* based on initialization configuration from the caller, and stubbed for all the locking operations in a non-pthread configuration. – WhozCraig Oct 12 '13 at 22:15
  • You could always punt, btw, and have the application provide a set of locks *for you*. The RSA BSAFE CryptoC-ME does this, for example, by requiring the application provide a callback function that dutifully uses it own "lock table", sized to a predefined `#define` magnitude specified by the library headers, with however many "locks" is needed. Each lock operation is passed to the callback by index, with a var stating what the operation request is (LOCK, UNLOCK). The application can then, knowing it is single threaded, simply provide a callback that always returns true and does nothing else. – WhozCraig Oct 12 '13 at 22:23

2 Answers2

3

After some testing, it seems that Linux already does what I want automatically! You only need to link against pthreads if you use threading, not if you just want pthread mutex support.

In this test case:

#include <stdio.h>
#include <errno.h>
#include <pthread.h>

int main()
{
  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

  if (!(errno = pthread_mutex_lock(&mutex))) { puts("Mutex locked!"); }
  else { perror("Could not lock mutex"); }

  if (!(errno = pthread_mutex_lock(&mutex))) { puts("Mutex locked!"); }
  else { perror("Could not lock mutex"); }

  return 0;
}

When compiling this without pthreads linked, I see "Mutex locked!" twice. Which indicates that pthread_mutex_lock() is essentially a non-op. But with pthreads linked, running this application will stall after the first time "Mutex locked!" is printed.

Therefore, I can use mutexes in my library where appropriate, and don't need to require pthreads to use, and no (signifigant?) overhead where it isn't needed.

Joe
  • 79
  • 1
  • 9
  • I tested this on the various BSDs I had handy as well. FreeBSD and NetBSD have the same semantics as Linux here, which is good, OpenBSD required -lpthread. On a side note, both FreeBSD and OpenBSD when linked with -lpthread did not hang at the second lock, but returned deadlock avoided instead. All the BSDs seem to also define __isthreaded in , so that can be checked to see if any threads are actually in use. – Joe Oct 13 '13 at 01:13
  • Regarding __isthreaded, turns out that NetBSD lacks it, as does Debian kFreeBSD. DragonFlyBSD turned out to match FreeBSD. For OpenBSD, to achieve what I want, one needs to utilize: int pthread_mutex_lock(pthread_mutex_t *mutex) __attribute__((weak)); – Joe Oct 13 '13 at 13:25
0

The usual solutions are:

  1. Use a #define switch to control at build time whether to call the pthreads functions or not, and have your build process create two versions of your library: one pthread-aware and one not, with different names. Rely on the user of your library to link against the correct one.

  2. Don't call the pthreads functions directly, but instead call user-provided lock and unlock callbacks (and thread-local-storage too, if you need that). The library user is responsible for allocating and calling the appropriate locking mechanisms, which also allows them to use a non-pthreads threading library.

  3. Do nothing at all, and merely document that user code should ensure that your library functions aren't entered at the same time from multiple threads.

glibc does something different again - it uses tricks with lazy binding symbols to call the pthreads functions only if they are linked into the binary. This isn't portable though, because it relies on specific details of the glibc implementation of pthreads. See the definition of __libc_maybe_call():

#ifdef __PIC__
# define __libc_maybe_call(FUNC, ARGS, ELSE) \
  (__extension__ ({ __typeof (FUNC) *_fn = (FUNC); \
                    _fn != NULL ? (*_fn) ARGS : ELSE; }))
#else
# define __libc_maybe_call(FUNC, ARGS, ELSE) \
  (FUNC != NULL ? FUNC ARGS : ELSE)
#endif
caf
  • 233,326
  • 40
  • 323
  • 462