Programming

Does std::steady_clock::now() Give Global Time in C++ Threads?

std::steady_clock::now() returns a global monotonic time point comparable across C++ threads, but now1 < now2 doesn't imply execution order or happens-before between operations like store and load in multithreading.

1 answer 1 view

Does std::steady_clock::now() return a value representing a global time point for the entire C++ program in a multithreaded context?

Consider this example:

cpp
#include <atomic>
#include <chrono>
#include <thread>

uint64_t timestamp() {
 auto now = std::chrono::steady_clock::now().time_since_epoch();
 return std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
}

int main() {
 std::atomic<long int> val;
 long int now1, now2;
 auto t1 = std::thread([&]() {
 val.store(1); // #1
 now1 = timestamp(); // #2
 });
 auto t2 = std::thread([&]() {
 now2 = timestamp(); // #3
 val.load(); // #4
 });
 t1.join();
 t2.join();
}

According to intro.execution p8, if evaluation A is sequenced before B, the execution of A precedes B. Thus, in t1, #1 precedes #2; in t2, #3 precedes #4.

time.clock.req states that now() returns a time_point representing the current point in time.

If now1 < now2, does this imply #1 executes before the time point of #4 (in terms of execution timing, not visibility)?

From the C++ abstract machine perspective:

  1. Does std::steady_clock::now() denote a global program time point across threads?
  2. If yes, does now1 < now2 confirm #1 executes before #4’s time point?

Yes, std::steady_clock::now() returns a time point from a global monotonic clock shared across all threads in a C++ program, making values from different threads directly comparable—they all reference the same program-wide epoch. But here’s the catch: even if now1 < now2 in your example, it doesn’t imply that operation #1 (#1) executed before the time point of #4, nor does it establish any happens-before relationship or memory visibility between threads. The clock measures real time passage, not program execution order or synchronization.

Contents


What is std::steady_clock::now()?


Ever wondered why C++ gives us std::steady_clock alongside system_clock? It’s designed for one thing: reliable interval measurements that never go backwards. Call std::chrono::steady_clock::now(), and you get a time_point tied to a fixed epoch—usually since program start or last reboot, but crucially, it ticks forward at a steady rate.

The C++ standard defines it clearly: this clock is monotonic, meaning if you snap two time points where the first call “happens before” the second (even across the same thread), t1 <= t2 always holds, and tick intervals stay constant. No leaps, no adjustments. Perfect for benchmarking loops or timeouts.

But in multithreaded code? That’s where questions like yours pop up. Does “steady” mean “global”? Let’s break it down.

std::steady_clock::now() as a Global Time Point Across Threads


Short answer: yes. All threads in your C++ program see the same steady_clock epoch. Grab now() from thread A and thread B—those time points are comparable directly. No offsets, no per-thread quirks.

Why? The clock is a program-wide bundle: shared duration, shared epoch, shared now() function. As cppreference explains, it’s a TrivialClock—simple, consistent, and monotonic across the board. Community tests on Stack Overflow confirm this: threads calling now() produce values you can order with < or > reliably.

Imagine two threads racing. Thread 1 hits now() at 1.23 seconds post-epoch. Thread 2 at 1.24. Boom—now1 < now2 tells you thread 1’s snapshot came first in real time. But real time isn’t the full story…

Clock Ordering vs. Execution Ordering


Here’s where it gets tricky. std::steady_clock::now() gives a global time point, sure. But does now1 < now2 mean operation #1 finished before #4’s clock snapshot? No.

Clocks measure wall time, not your code’s execution flow. The C++ execution model uses “sequenced before” within threads and “happens-before” across them. Your now() calls? They’re just observations. #1 sequenced-before #2 in t1, #3 sequenced-before #4 in t2. But now1 < now2 doesn’t bridge threads—it can’t force #1 to precede #4’s timing.

A deeper Stack Overflow discussion nails it: clocks aren’t sync primitives. If #4’s load sees #1’s store (or not), that’s up to memory ordering, not clock math. now1 < now2 says the measurement points ordered that way, but interleaving could have #4 peek before #1 wrote, even if clocks disagree.

Think of it like photographers at a race. Snapshots timestamp the moment, but don’t dictate who crossed the line when.

Analyzing the Multithreaded Example


Let’s dissect your code. Two threads, an atomic val, and timestamps around store/load.

In t1: val.store(1); (#1) then now1 = timestamp(); (#2). Sequenced-before guarantees #1 before #2’s clock read.

In t2: now2 = timestamp(); (#3) then val.load(); (#4). Same deal.

Join them. Suppose now1 < now2. Does this prove #1 hit before #4’s time point? Nope. Why?

  • Clock gives real-time ordering of the now() calls themselves.
  • But #4 could execute before #1 in the scheduler’s whims, as long as memory order allows.
  • No synchronization—no mutex, no barrier—so no happens-before chain.

From another thread-safety angle, even if val.load() sees the store later, clocks don’t retroactively order the ops. It’s measurement, not causation.

Run it a thousand times. You’ll see now1 < now2 often, but val.load() might still grab 0 half the time. Clocks lie about execution?

Practical Implications for C++ Developers


So, use steady_clock::now() for what it’s great at: cross-thread interval diffs, like “how long did this parallel task take?” Compare end - start from any thread—accurate, monotonic.

But for ordering guarantees? Reach for atomics with memory_order_seq_cst, fences, or mutexes. Want #1 visible before #4? Seq-cst store/load, or a release-acquire pair.

In debugging? Log with steady_clock timestamps—they’re globally consistent for sequencing events post-facto. “Event A at 1.23s, B at 1.24s.” Helpful, without implying causality.

Performance tip: steady_clock is cheap—hardware timers under the hood. No syscalls like system_clock.

Sources


  1. Does the returned value from std::steady_clock::now() denote a point in global time — Detailed analysis of clock comparability and execution ordering limits: https://stackoverflow.com/questions/79861495/does-the-returned-value-from-stdsteady-clocknow-denote-a-point-in-global
  2. [time.clock.req] — Official C++ standard requirements for monotonic clocks and time_point semantics: https://eel.is/c++draft/time.clock.req
  3. [intro.execution] — C++ execution model on sequenced-before and multithreading limits: https://eel.is/c++draft/intro.execution#8
  4. std::chrono::steady_clock — Reference on steady_clock monotonicity and TrivialClock compliance: https://en.cppreference.com/w/cpp/chrono/steady_clock
  5. Is the value of steady_clock::now from multiple threads consistent — Discussion on clock as measurement vs synchronization: https://stackoverflow.com/questions/65046402/is-the-value-of-steady-clocknow-from-multiple-threads-consistent-with-memory-o
  6. Is steady_clock monotonic across threads? — Confirmation of cross-thread monotonicity with standard quotes: https://stackoverflow.com/questions/40930541/is-steady-clock-monotonic-across-threads

Conclusion


std::steady_clock::now() delivers a global time point across threads—comparable, monotonic, program-wide. Use it confidently for timing diffs. But in multithreaded scenarios like your example, now1 < now2 won’t confirm #1 precedes #4’s execution timing; that’s for memory models to handle. Stick to proper sync for ordering, clocks for measurement—you’ll avoid subtle races that way.

Authors
Verified by moderation
Moderation
Does std::steady_clock::now() Give Global Time in C++ Threads?