8

Consider the following codesnippet:

#include <iostream>
#include <condition_variable>
#include <chrono>
#include <mutex>

int main () {
  std::mutex y;
  std::condition_variable x;
  std::unique_lock<std::mutex>lock{y};
  int i = 0;
  auto increment = [&] {++i; return false;};
  using namespace std::chrono_literals;

  //lock 5s if increment returns false
  //let's see how often was increment called?
  x.wait_for(lock, 5s, increment);
  std::cout << i << std::endl;

  //compare this with a simple loop:
  //how often can my system call increment in 5s?
  auto const end = std::chrono::system_clock::now() + 5s;
  i = 0;
  while (std::chrono::system_clock::now() < end) {
    increment();
  }
  std::cout << i;
}

As I understand wait_for, i should be O(1) after wait_for (let's assume spurious unlocks are rare).

However, I get
i ~= 3e8 for kernel 4.17.14, Intel(R) Core(TM) i7-6700 CPU @ 3.40GHz,
i ~= 8e6 for kernel 3.10.0, Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz.

This sounds funny, so i check by comparing with a simple loop that runs 5 seconds. Roughly same results for i, only a 5-10% difference.

Question:
what is wait_for doing? Does it work as expected and I just understood cppreference wrong, or did I mess up?

Second, (optional) question: Where does this enormous difference in i come from?

Additional info: (gcc7.3, gcc8.2, clang6.0), flags: -O3 --std=c++17 all yield comparable results.

nnolte
  • 1,628
  • 11
  • 25
  • 1
    Can't reproduce on [Wandbox](https://wandbox.org/permlink/zIzoS6cRG00zWlVd). – xskxzr Aug 15 '18 at 13:05
  • 1
    [I cannot reproduce on wandbox](https://wandbox.org/permlink/HsF804u6PQempCTz). I get `2` for every version of clang and gcc I've tried for `x.wait_for(lock, 5s, increment); std::cout << i << std::endl;`. I also get `3` on my local machine with MSVS. – NathanOliver Aug 15 '18 at 13:06
  • 1
    I get 50K on 2.6.32 redhat kernel – Slava Aug 15 '18 at 13:07
  • 3
    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=75402, https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58929 could be relevant? – Alan Birtles Aug 15 '18 at 13:14
  • 1
    @AlanBirtles indeed, adding -pthread fixed the "bug"(?) Thanks a lot. Let's see whether it is confirmed at some point.. – nnolte Aug 15 '18 at 13:18
  • FWIW, I think for Linux pthreads, spurious wakeup can only happen by signal interruption (and the return code will be `EINTR` which could be handled transparently, but is deliberately not, in order to allow the program to deal with signals more easily), and a scenario where a thread starts to wait just as another thread is currently being woken up (i.e. requiring at least two waiters). In the latter case, it is or was possible that both threads continue as the result of just one call to `pthread_cond_signal`. I don't even know if this still applies to the current implementation, though. – Arne Vogel Aug 15 '18 at 14:20

3 Answers3

5

libstdc++ has an unfortunate ability to compile and seemingly work without pthread however it wont function correctly.

See this libstdc++ bug: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=58929

You need to add "-pthread" to your compile command.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
1

You need to add -pthread flag to gcc when compiling, for example on gcc 5.1.0 on RE:

without pthread: 49752692
with pthread: 2
Slava
  • 43,454
  • 1
  • 47
  • 90
1

You need to link to the pthread library using the -pthread flag to g++:

g++ cond_test.cpp -pthread

Most linux systems require you to link to the pthread library to use threading features. However programs using standard C++ threading seems to link successfully without explicitly linking to pthread, and instead produce undefined behavior at runtime (It often crashes, but with this code it seems not to, but instead produce unexpected behavior).

Example for this code:

$ g++ t.cpp  && ./a.out
5817437
18860410
$ g++ t.cpp  -pthread && ./a.out
2
19718764
nos
  • 223,662
  • 58
  • 417
  • 506
  • 1
    It might be worth pointing out that the `-pthread` flag does more than just link libpthread - it may also introduce other necessary flags, defines in code, etc. I've seen people do `-lpthread`, which is veeeeery similar, but wrong. – BoBTFish Aug 15 '18 at 13:21
  • @BoBTFish what else does -pthread do? since the assembly seems to be similar with and without `-pthread` (objdump & compiler explorer) – nnolte Aug 15 '18 at 13:29
  • @kawillzocken It may set flags for compilation stages other than linking. Depends on the platform, but typically on Linux it also defines `_REENTRANT`: https://stackoverflow.com/a/2127819/1171191 – BoBTFish Aug 15 '18 at 13:46