4

When I write code that uses, for example, std::promise, and I don't include the PThread library in GCC, I get an exception thrown rather than a linker error. For example:

void product(std::promise<int> intPromise, int a, int b)
{
    intPromise.set_value(a * b);
}
int main()
{
    int a = 20;
    int b = 10;
    std::promise<int> prodPromise;
    std::future<int> prodResult = prodPromise.get_future();
    product(std::move(prodPromise), a, b);
    std::cout << "20*10= " << prodResult.get() << std::endl;
}

If I compile this code without -pthread, the following exception is thrown:

terminate called after throwing an instance of 'std::system_error'
  what():  Unknown error -1
Aborted (core dumped)

If std::promise using the pthread library internally, then it should throw linkage error right if I don't give the -pthread commandline option to g++. But it's compiling without any errors and while running I am getting the above issue.

Nicol Bolas
  • 449,505
  • 63
  • 781
  • 982
goodman
  • 424
  • 8
  • 24
  • 1
    @TedLyngmo: He's asking (thought it takes way too long to get to the point) why it throws exceptions rather than fails to link entirely. – Nicol Bolas Jun 10 '20 at 16:47
  • 1
    libstdc++ has a pluggable threads implementation ([gthreads](https://gcc.gnu.org/onlinedocs/libstdc++/manual/ext_concurrency_impl.html)). The pluggability is handled at run time. – rustyx Jun 10 '20 at 16:50
  • 1
    Well, I can explain [how](https://github.com/gcc-mirror/gcc/blob/master/libgcc/gthr-posix.h#L252), but still would be nice to know why. – KamilCuk Jun 10 '20 at 17:08
  • `clang++` + `libc++` [works fine](https://godbolt.org/z/jxGf-i). Bug in `libstdc++`? – Ted Lyngmo Jun 10 '20 at 17:12
  • @TedLyngmo, not a bug, but an implementation detail. – Evg Jun 11 '20 at 10:00
  • @Evg Ah, I see. Nice answer below! – Ted Lyngmo Jun 11 '20 at 10:13

1 Answers1

3

The reason for this is that libstdc++ uses the so called weak references.

We can easily trace why your particular code example throws an exception. set_value() calls std::call_once. That function in its implementation has the line*:

int e = gthread_once(&once.M_once, &once_proxy);

where gthread_once is:

static inline int gthread_once(gthread_once_t *once, void (*func)(void))
{
  if (gthread_active_p())
    return ...
  else
    return -1;
}

gthread_active_p returns false, that's why gthread_once returns -1, which is mentioned in the exception string.

Now let's take a look at gthread_active_p:

static __typeof(pthread_key_create) gthrw_pthread_key_create
    __attribute__ ((weakref("__pthread_key_create")));

static inline int gthread_active_p(void)
{
  static void *const gthread_active_ptr = (void *)&gthrw_pthread_key_create;
  return gthread_active_ptr != 0;
}

gthrw_pthread_key_create is a weak reference to __pthread_key_create. If there is no symbol __pthread_key_create found by the linker, &gthrw_pthread_key_create will be a null pointer, if __pthread_key_create is found, gthrw_pthread_key_create will be an alias for it. __pthread_key_create is exported by the pthreads library.

The standard library source code also contains the following comment:

For a program to be multi-threaded the only thing that it certainly must be using is pthread_create. However, there may be other libraries that intercept pthread_create with their own definitions to wrap pthreads functionality for some purpose. In those cases, pthread_create being defined might not necessarily mean that libpthread is actually linked in.

For the GNU C library, we can use a known internal name. This is always available in the ABI, but no other library would define it. That is ideal, since any public pthread function might be intercepted just as pthread_create might be. __pthread_key_create is an "internal" implementation symbol, but it is part of the public exported ABI. Also, it's among the symbols that the static libpthread.a always links in whenever pthread_create is used, so there is no danger of a false negative result in any statically-linked, multi-threaded program.


* Some underscores are removed and macros are expanded to improve readability.

Evg
  • 25,259
  • 5
  • 41
  • 83