The number people quote is the register save, which is the cheapest part of it.
what the kernel must do what it really costs
---------------------------------- ------------------------------------------
save registers, switch stacks ~100 ns of direct instructions
switch page tables on a process TLB entries invalidated, tens of
switch microseconds of refill misses afterwards
scheduler bookkeeping the new task's working set is cold in L1/L2
typical, one modern core
plain function call ~1 ns
thread switch, same address space 1-5 us including cache effects
process switch 3-10 us
The indirect cost dominates, and it is invisible to a microbenchmark that
switches between two tiny loops with no working set. Real code touches memory, so
after a switch the caches hold the previous task's data and the incoming task
takes thousands of misses before it runs at full speed.
That arithmetic is what decides thread-pool sizing. A thread that blocks on a
lock or a socket a hundred thousand times a second spends the majority of its
wall clock inside the scheduler rather than in your code, which is why the fix
for a slow, heavily contended service is usually fewer threads doing longer
units of work rather than more threads.
It is also the argument for asynchronous I/O and for user-space scheduling —
green threads, goroutines, virtual threads. They do not make the kernel switch
faster; they avoid needing one, because a task that blocks is parked by the
runtime while the operating-system thread underneath it keeps running. The cost
that remains is that any genuinely blocking call inside such a task pins the
carrier thread and quietly removes the benefit.