When does time.thread_time() answer a question that time.process_time() cannot?
answer
- Same quantity, different scope
- One counter is per process, one per caller
- Neither counts waiting
- Attributing CPU to one worker thread
- thread_time is not on every Unix
basics
~20 stime.process_time() sums CPU time across every thread in the process, so one busy thread is hidden among the others. time.thread_time() reports CPU time for the calling thread only, which is how you charge processor cost to one worker.
solid answer
~40 s`time.process_time()` is a whole-process number: it adds up user plus system CPU for all threads, so in a multi-threaded program you cannot tell whether one thread burned ten seconds or ten threads burned one each. `time.thread_time()` returns the CPU time of the *calling thread* alone, again excluding any time that thread spent sleeping or blocked. Reading it at the start and end of a worker's body charges CPU cost to that worker specifically, which is what you want when several threads share a process and only one is suspected of burning cycles. Both have `_ns` integer variants. `thread_time()` is available on Linux, macOS and Windows but is not guaranteed on every Unix, so probe with `hasattr` or `time.get_clock_info` before relying on it in portable code.
code
python · 12 linesimport threading
import time
def worker():
start = time.thread_time()
sum(range(3_000_000))
time.sleep(0.2)
print(f"thread CPU used: {time.thread_time() - start:.3f}s")
t = threading.Thread(target=worker)
t.start()
t.join()go deeper
Know that both calls report processor time rather than elapsed time, and that the difference is scope: the whole process versus the one thread that called it. Neither counts time spent sleeping.
Explain the mechanics and the guard rails: sum-across-threads versus caller-only, the _ns variants, platform availability on some Unix systems, and coarse resolution over short intervals.
Show how you would attribute CPU inside a running multi-threaded service with two cheap readings instead of attaching a profiler, and how you read wall, process and thread numbers together to classify a slowdown.
Own the decision of what a long-running service records by default — per-thread CPU accounting on named worker threads costs almost nothing and turns 'which thread is eating the box' into a query rather than an investigation.
`time.process_time()` and `time.thread_time()` both measure **CPU time** — processor seconds actually charged to running code, user plus system, with sleeping and blocking excluded. They differ only in the scope they sum over, and that scope is the whole point. ## The scope difference `time.process_time()` is a *process-wide* counter. Every thread's CPU time contributes to it. In a single-threaded program that is unambiguous. In a program with a pool of workers it becomes an aggregate that answers "how much processor did this process consume" and refuses to answer "which thread consumed it". A process reporting twelve CPU seconds might be one thread grinding for twelve seconds or twelve threads working for one second each; those two situations call for completely different fixes. `time.thread_time()` is the *per-thread* counter. Called inside a thread, it returns the CPU time charged to that thread since an undefined origin, so a difference between two readings taken in the same thread is that thread's own processor cost. It is the natural instrument for attributing CPU to a specific worker. ```python import threading import time def worker(): start = time.thread_time() sum(range(3_000_000)) # burns this thread's CPU time.sleep(0.2) # burns nobody's CPU print(time.thread_time() - start) t = threading.Thread(target=worker) t.start() t.join() ``` The printed number reflects the arithmetic only; the sleep contributes nothing, and work done concurrently in other threads contributes nothing either. ## What each one is good for * **Whole-process capacity questions** — "is this service CPU-bound?", "did the optimisation reduce processor cost?" — belong to `process_time()`. It is also the number that lines up with what a container CPU quota accounts for. * **Attribution inside a process** — "the background flush thread looks innocent, prove it" — belongs to `thread_time()`. Instrument the thread body, log the delta when the thread exits or on a periodic tick, and you have per-thread CPU without a profiler attached. * **Neither** measures waiting. A thread blocked on a socket for a minute adds zero to both. If a worker is slow but its `thread_time()` delta is tiny, it was blocked, and wall-clock accounting with `time.perf_counter()` is the measurement you actually need. ## Interpreting the numbers together Three readings taken around the same interval — wall via `perf_counter()`, process CPU, and this thread's CPU — form a small diagnostic table: * thread CPU ≈ wall → this thread computed the whole time. * thread CPU ≪ wall, process CPU ≈ wall → this thread waited while *other* threads computed. * thread CPU ≪ wall, process CPU ≪ wall → the process as a whole was waiting; look at I/O, locks and subprocesses. * process CPU > wall → real parallelism: several threads ran at once, either in C code that released the GIL or on a free-threaded build, which Python 3.14 supports officially. ## The caveats worth stating in an interview **Availability.** `thread_time()` is documented as available on Linux, macOS and Windows, and *not* guaranteed on every Unix, unlike `process_time()`. Portable code guards it: ```python import time have_thread_time = hasattr(time, "thread_time") ``` `time.get_clock_info("thread_time")` reports the clock's resolution and whether it is monotonic on the current platform. **Resolution.** Per-thread CPU accounting on some platforms is coarser than the wall clock, so a delta measured over a millisecond of work can be dominated by quantisation. Measure over a meaningful stretch of work, or use `time.thread_time_ns()` and accumulate. **It follows the thread, not the task.** In asyncio, many coroutines share one thread; `thread_time()` charges all of them together and cannot separate one task's CPU from another's. Per-task attribution needs task-aware instrumentation, not a thread clock. **It does not see subprocesses.** CPU burned by a child process is charged to that child; neither clock reports it. `os.times()` exposes children's CPU on Unix if you need it. ## Why it is a differentiator rather than a gate Most Python programs never need per-thread CPU: single-threaded jobs get everything they need from `perf_counter()` plus `process_time()`, and multi-threaded services usually reach for a sampling profiler that already breaks down by thread. `thread_time()` earns its place in the narrow, real case where you must prove which of several long-lived threads in a running process is spending the processor, using two cheap readings and no profiling overhead at all.
- A worker thread takes ten seconds of wall time but its time.thread_time() delta is 0.05 seconds. What happened?The thread spent essentially all ten seconds blocked, not computing — waiting on a socket, a lock, a queue `get`, or a subprocess. CPU clocks exclude blocked time by definition, so no amount of CPU profiling will explain the ten seconds. The next measurement is wall-clock accounting around each step of the thread's body to see which wait dominates.
- Why can't time.thread_time() separate the CPU cost of two asyncio tasks?Tasks on one event loop share a single OS thread, so the thread clock charges all of them to the same counter. Reading it around one coroutine's body also captures every other task the loop interleaved during its awaits. Separating them requires task-aware instrumentation — timing around the synchronous stretches between awaits, or a profiler that understands coroutine frames.
saying these in an interview costs you the question
- Thinking process_time reports a single thread's CPU
- Expecting thread_time to include blocked or sleeping time
- Assuming thread_time exists on every platform
- Using thread_time to attribute cost per asyncio task
- Expecting either clock to count a child process's CPU