Is checking the tick of a monotonic clock in C++ wait free?

Viewed 633

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 ?

2 Answers

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.

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.

Related