2

Say we have a very basic code to check an elapsed interval

#include <iostream>
#include <chrono>
 
int main()
{
        auto start = std::chrono::steady_clock::now();
        // some work
        auto end = std::chrono::steady_clock::now();
        std::chrono::duration<double> diff = end-start;
        std::cout << "The interval is " << diff.count() << "\n";
}

Does each call to std::chrono::steady_clock::now() block other threads ? Is it considered lock free, or even wait free ? Does a call to std::chrono::steady_clock::now() ever starve or block any other thread ?

I am most interested in what the specification guarantees for std::chrono::steady_clock::now() . If it does not guarantee it to be wait free, is there any way to get a tick of a monotonic clock on MSVC/gcc/clang that is guaranteed to be wait free ?

Josh Weinstein
  • 2,788
  • 2
  • 21
  • 38
  • 3
    I sense an underlying problem here... What is the real problem you have? – Some programmer dude Jul 02 '21 at 23:04
  • `clock::now()` usually doesn't have anything to do with other threads at all.... – Mooing Duck Jul 02 '21 at 23:22
  • @Someprogrammerdude I am working on some lock free or wait free algorithms for version control of objects in a database, and I am wondering if checking the time of a monotonic clock can block other threads – Josh Weinstein Jul 02 '21 at 23:23
  • 1
    The only thing in C++ I'm aware of that blocks other threads are (A) the random apis, and (B) locks. Well, and file I/O but that's due to saturation rather than locking. – Mooing Duck Jul 02 '21 at 23:25
  • Is this question about one particular implementation of `std::chrono::steady_clock::now` or about the specification of the API? (because if the standard doesn't specify explicitly one way or another, then different implementations might behave different ways) – Jeremy Friesner Jul 02 '21 at 23:35
  • it's whether or not the specification of `steady_clock` guarantees it, and if not, is there some way on gcc/clang/msvc to guarantee it will always be wait free ? – Josh Weinstein Jul 02 '21 at 23:40
  • @JoshWeinstein What resolution do you need? If, for example, you only need second resolution, you can have a thread that increments an atomic integer every second. But most likely, you should just stop thinking about wait free and start thinking about optimal. If an algorithm that isn't wait free performs better, why would you want a wait free algorithm? (And what kind of crappy standard would *require* a worse algorithm?! A standard that mandated a lock free algorithm even where it performs worse would be a *bad* standard.) – David Schwartz Jul 02 '21 at 23:42
  • Would it be a problem if `steady_clock::now()` made a system call? That's the most likely "gotcha" I can think of, but I don't know if that would be problematic for the questioner's use-case. – Jeremy Friesner Jul 02 '21 at 23:47
  • It would need to be in the micro second resolution. @JeremyFriesner if it made it every time ? yes, that would not be suitable. But rarely ? that would be fine. – Josh Weinstein Jul 02 '21 at 23:55
  • 1
    @JoshWeinstein: It's entirely possible that it has to make a system call every time. For instance, this would be the case on Linux prior to the advent of vsyscall/vdso, or where these are disabled. Other systems may simply not have any clock hardware that unprivileged processes can use. And on a system with a "big kernel lock", it might very well be the case that two threads can't make that system call simultaneously. (But that would also be true, trivially, on a uniprocessor system.) – Nate Eldredge Jul 03 '21 at 00:11
  • 1
    Usually the concern about whether something is "wait free" would have to do with whether there is a possibility of deadlock. That doesn't seem to be an issue here - no matter how many threads call `now()` simultaneously, one of them will certainly make progress. So I'm not sure what significance the "wait free"-ness of `now()` would have, except in terms of performance, and the C++ standard certainly doesn't make any concrete promises about performance - other than big O complexity for algorithms, performance isn't considered "observable behavior". – Nate Eldredge Jul 03 '21 at 00:23
  • 1
    And thinking again about the uniprocessor case - such an implementation can certainly be conforming, and in that case, *everything* a thread does will "block" other threads in your informal sense. So there can't possibly be any guarantee that `now()` doesn't do so. – Nate Eldredge Jul 03 '21 at 00:26
  • Ideally, this would only run on a server tier proceesor, such as an xeon ice lake. But either way, it seems from the above comments, to just have a thread get the tick of the monotonic clock once when it begins processing a job, rather than always getting a new tick when doing comparisons. – Josh Weinstein Jul 03 '21 at 00:50
  • Well, fewer calls is always better than more calls. But on modern hardware and a well-configured OS, it's quite possible for such a call to be very efficient, reading an internal CPU timer and involving no context switch or contention. I'm just saying that if you're looking for a guarantee of such efficiency, the C++ standard is the wrong place to be looking. – Nate Eldredge Jul 03 '21 at 01:09

2 Answers2

3

The C++ standard is silent on this issue.

Rationale: Every OS has some way of getting monotonic time. Down at the hardware level this is a counter per clock cycle. At the OS level this may or may not translate to scaling to some convenient units such as nanoseconds.

The problem <chrono> solves is that the OS API for getting this measure of time is different as one moves across OS's, and sometimes even as one moves from one version to the next. <chrono> makes the underlying API uniform for the C++ programmer. Nothing more, nothing less.

So to answer this question, you really have to drop down to the OS level timing API.

Howard Hinnant
  • 206,506
  • 52
  • 449
  • 577
1

So, let's see how std::chrono::steady_clock::now() has been implemented in gcc. The answer can be found at https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/src/c%2B%2B11/chrono.cc

    steady_clock::now() noexcept
    {
#ifdef _GLIBCXX_USE_CLOCK_MONOTONIC
      timespec tp;
      // -EINVAL, -EFAULT
#ifdef _GLIBCXX_USE_CLOCK_GETTIME_SYSCALL
      syscall(SYS_clock_gettime, CLOCK_MONOTONIC, &tp);
#else
      clock_gettime(CLOCK_MONOTONIC, &tp);
#endif
      return time_point(duration(chrono::seconds(tp.tv_sec)
                 + chrono::nanoseconds(tp.tv_nsec)));
#else
      return time_point(system_clock::now().time_since_epoch());
#endif
    }

from which one can infer that it delegates (or more precisely: can delegate) the job to C's standard function clock_gettime. Its manual page https://www.man7.org/linux/man-pages/man3/clock_gettime.3.html has a section "attributes", and there the value of "thread safety" is "MT-safe". This is explained here: https://www.man7.org/linux/man-pages/man7/attributes.7.html

Being MT-Safe does not imply a function is atomic, nor that it uses any of the memory synchronization mechanisms POSIX exposes to users.

In other words, you have no guarantees other that the function can be safely called in a multi-threaded environment.

At this point you can appreciate the fact that gcc/clang are open-source and see yourself what's inside clock_gettime's implementation, as I did for steady_clock::now(), but you'll never get such an answer for closed-source compilers, nor have a guarantee the implementation used by gcc/clang will never change.

But if you're after using a C++ clock in a lock-free algorithm, then you may be focusing on performance. Then, please read:

If your solution is to be multiplatform and essentially hardware-agnostic, then the penalty for using the clock may be similar to or even larger than that of using mutexes for synchronization. I don't know if this is the case, but this is certainly something you need to take into account.

zkoza
  • 2,644
  • 3
  • 16
  • 24