skip to content

What does signal.setitimer with signal.ITIMER_REAL do that signal.alarm cannot?

level: middleimportance: nice to knowfreq 18%

answer

  1. One call, three different clocks
  2. Floats instead of whole seconds
  3. A second argument makes it repeat
  4. Wall time versus CPU time
  5. Same underlying timer as the simpler call

basics

~10 s

signal.setitimer takes float seconds, so it can arm a sub-second deadline, and an optional interval argument that re-arms the timer for a repeating SIGALRM. signal.alarm is one-shot and whole-seconds. signal.getitimer reports what is left.

solid answer

~40 s

`signal.setitimer(which, seconds, interval=0.0)` arms one of three per-process interval timers. With `signal.ITIMER_REAL` it counts wall-clock time and delivers `SIGALRM`, exactly like `signal.alarm` — but `seconds` is a float, so `0.25` is expressible, and a non-zero `interval` makes the timer re-arm itself after every delivery, giving a periodic tick instead of a one-shot deadline. Passing `0` as `seconds` disarms it, and `signal.getitimer(signal.ITIMER_REAL)` returns the `(delay, interval)` pair still pending. The other two flavours count CPU rather than wall time: `signal.ITIMER_VIRTUAL` counts the process's user-mode time and raises `SIGVTALRM`, `signal.ITIMER_PROF` counts user plus system time and raises `SIGPROF`. `ITIMER_REAL` and `signal.alarm` are the same underlying timer, so mixing them clobbers.

code

python · 10 lines
python
import signal, time

def on_alarm(signum, frame):
    print("tick")

signal.signal(signal.SIGALRM, on_alarm)
signal.setitimer(signal.ITIMER_REAL, 0.2, 0.2)   # first fire, then repeat
time.sleep(0.7)
signal.setitimer(signal.ITIMER_REAL, 0)          # disarm
print("pending:", signal.getitimer(signal.ITIMER_REAL))

go deeper

for a junior

Recall the shape rather than the detail: there is a richer timer call than signal.alarm, it accepts fractional seconds, and a second argument makes it repeat. Knowing that signal.alarm rejects floats is enough to reach for it when you need one.

for a middle

Explain the full signature, what disarming looks like, and the difference between the wall-clock and CPU-time flavours. Be able to say why the two spellings of the wall-clock timer collide and why the return value of setitimer matters.

for a senior

Show the operational judgement around a repeating timer: keep the handler to a flag flip, know that deliveries collapse rather than queue, and be wary of installing a process-wide SIGALRM tick in code that other libraries share.

for a principal

Own the choice of mechanism. A repeating signal perturbs the main thread by design, which is right for sampling and wrong for routine scheduling; decide when a plain timer thread is the safer default and where process-wide SIGALRM ownership is allowed to live.

`signal.alarm` is the minimal interface to a much richer Unix facility. `signal.setitimer` exposes the rest of it, and the three things it adds are fractional resolution, repetition, and a choice of clock. ### The signature ```python signal.setitimer(which, seconds, interval=0.0) ``` `which` selects one of three timers. `seconds` is a float giving the delay until the first delivery; passing `0` disarms the timer. `interval` is a float giving the gap between subsequent deliveries; leave it at `0.0` for a one-shot. The return value is the `(delay, interval)` pair that was previously armed, and `signal.getitimer(which)` reads the same pair without changing anything. Invalid values raise `signal.ItimerError`, a subclass of `OSError` — `signal.setitimer(signal.ITIMER_REAL, -1)` is the easy way to see it. ### The three timers **`signal.ITIMER_REAL`** counts wall-clock time and delivers `SIGALRM`. This is the one you want for a deadline: it does not care whether your process was running, sleeping, or descheduled. It is also, importantly, the *same* kernel timer that `signal.alarm` drives. Arming one cancels the other, and `signal.getitimer(signal.ITIMER_REAL)` will show the remaining time of an alarm set with `signal.alarm`. Treat them as two spellings of one resource. **`signal.ITIMER_VIRTUAL`** counts only the time the process spends executing in user mode, and delivers `SIGVTALRM`. A process blocked on I/O accumulates nothing. This is a profiling clock, not a deadline clock: it answers "how much CPU have I burned" rather than "how much time has passed". **`signal.ITIMER_PROF`** counts user plus system time and delivers `SIGPROF`. It is what sampling profilers historically used, since a tick every few milliseconds of CPU gives you a stack sample proportional to CPU consumption. Both of the CPU-based timers are per-process, and in a multi-threaded process the accounting sums across threads while the handler still runs only on the main thread. ### Fractional deadlines The practical reason to reach for `setitimer` in ordinary code is that `signal.alarm(0.5)` is a `TypeError` — it takes an integer count of seconds. A one-second floor is far too coarse for bounding, say, a single sensor read that normally completes in eight milliseconds. `signal.setitimer(signal.ITIMER_REAL, 0.05)` gives you the deadline you actually meant. Resolution beyond that is the kernel's business; the value is rounded to the timer's granularity, and delivery is a lower bound, not a promise — a busy machine can be late. ### Repetition, and the discipline it needs A non-zero `interval` turns the timer into a heartbeat. The classic uses are a watchdog that checks a liveness flag, a periodic flush, and a sampling profiler. Two rules keep it safe. First, **the handler must be tiny**. It runs in the main thread between bytecodes, so everything else in that thread is stalled while it runs, and a handler that takes longer than the interval means ticks arrive faster than you can consume them. The signal is not queued: several deliveries during one slow handler collapse into one. So set a flag, append to a preallocated list, or bump a counter — and do the real work in the normal control flow. Second, **disarm explicitly**. `signal.setitimer(signal.ITIMER_REAL, 0)` stops it. A repeating timer left armed keeps interrupting the process for its entire remaining life, which is a far noisier failure than a stale one-shot alarm. ### When to prefer something else A repeating `SIGALRM` is main-thread-only and Unix-only, and it perturbs whatever the main thread is doing. If all you need is "call this function every N seconds" and you are not trying to interrupt anything, a `threading.Timer` that re-arms itself, or a plain loop in a dedicated thread, is simpler, portable, and does not collide with any other user of `SIGALRM` in the process. Reach for `setitimer` when the point is precisely to interrupt the main thread — a deadline on a call that offers none, or a sampling tick that must land inside whatever code is running.

  • How would you build a periodic watchdog tick with it, and what belongs in the handler?
    Arm `signal.setitimer(signal.ITIMER_REAL, 0.5, 0.5)` and keep the handler to a single cheap statement — set a flag or bump a counter. It runs in the main thread between bytecodes, so any real work there stalls the program, and ticks that arrive during a slow handler are collapsed rather than queued. Disarm with a `seconds` of 0 when you are done.
  • What is the difference between signal.ITIMER_VIRTUAL and signal.ITIMER_REAL?
    ITIMER_REAL counts wall-clock time and delivers SIGALRM, so it fires even while the process is blocked on I/O — that is what you want for a deadline. ITIMER_VIRTUAL counts only user-mode CPU time and delivers SIGVTALRM, so a process waiting on a socket accumulates nothing. ITIMER_PROF sits between them, counting user plus system time and delivering SIGPROF.
  • Can signal.alarm and signal.setitimer(signal.ITIMER_REAL, ...) be used together?
    Not independently — they drive the same per-process timer. Arming either one cancels whatever the other had pending, and signal.getitimer(signal.ITIMER_REAL) will report the remaining time of an alarm armed with signal.alarm. Pick one spelling for a codebase; mixing them across a library boundary is how a deadline quietly disappears.

saying these in an interview costs you the question

  • Thinks signal.alarm accepts a float delay
  • Believes setitimer and alarm are independent timers
  • Expects a non-zero interval argument to fire only once
  • Assumes ITIMER_VIRTUAL counts wall-clock time
  • Does substantial work inside a repeating handler
  • Forgets that a repeating timer must be disarmed explicitly

context