The Big Picture: A Taxi Rank

Imagine a town where, for every passenger, a taxi company must build a brand-new taxi, drive the passenger, then scrap the taxi. Crazy! A smarter company keeps a row of taxis waiting at a rank. A passenger arrives, takes the first free taxi, and when the ride ends, the taxi returns to the rank for the next passenger.

A thread pool is that taxi rank. The taxis are threads. The passengers are tasks (small jobs).

   Tasks arrive           QUEUE              POOL OF THREADS
   ─────────────►     ┌──────────┐         ┌───────────────┐
   (small jobs)       │ job job  │ ──────► │ thread 1 busy │
                      │ job job  │ ──────► │ thread 2 busy │
                      │ job      │ ──────► │ thread 3 idle │
                      └──────────┘         │ thread 4 idle │
                                           └───────────────┘
                      Threads are reused. They are not destroyed after each job.

9.1 What a Thread Pool Is

What it is: A thread pool is a ready-made group of worker threads that take jobs from a queue, run them one by one, then go back for more. Your program says what to do. The pool decides which thread does it and when.

How it works:

 1. The program SUBMITS a task (a function plus its data) to the pool.
 2. The task goes into a QUEUE.
 3. A free worker thread picks the task, runs it.
 4. When finished, the worker does NOT exit. It looks at the queue again.
 5. If the queue is empty, the worker sleeps (state Waiting, no CPU use).
 6. If work piles up, the pool may add threads (up to a limit).
    If threads stay idle for a long time, the pool may remove some.

Terms:

TermPlain meaning
Task / work itemOne small job: a function and its data
Worker threadA pool thread that runs tasks
QueueThe waiting list of tasks
Minimum / maximum threadsThe size limits the pool tries to stay within
CallbackThe function the pool calls to run your task

Typical uses:

  • A web server handling many short requests
  • Asynchronous I/O completions: "the file read finished, run this code"
  • Timers: "run this function in 5 seconds"
  • Splitting a big job into many small parts (for example, processing 1,000 images)

9.2 Why Thread Pools Exist (Reuse Overhead)

What it is: The pool exists because creating and destroying a thread for each small job is wasteful.

What one CreateThread costs (see Section 2.3):

CostDetail
MemoryA stack (about 1 MB of address space reserved, part of it actually used) plus kernel memory
Kernel workBuild the thread object, set it up, add it to the scheduler
StartupPer-thread data, runtime initialisation (for C/C++ and .NET)
TeardownFree the stack, close handles, remove from the scheduler

If a task only takes 0.1 ms, but creating the thread takes 0.1 ms or more, then half your time is overhead. With a pool, the creation cost is paid once and shared by thousands of tasks.

The bigger danger: too many threads.

 A server with 1,000 simultaneous requests, one new thread each:
   - 1,000 stacks of memory (about 1 GB of address space)
   - Only 8 logical processors available
   - 1,000 threads fight for 8 cores ► heavy context switching (Section 6)
   - Everything gets slower for everyone

A pool limits the number of active threads (near the number of cores for CPU-heavy work), so extra tasks wait their turn in the queue instead of overwhelming the CPU.

Benefits in a table:

BenefitWhy
Faster task startA thread is already waiting; no creation cost
Lower memory useA small, fixed number of stacks
Less context switchingThread count stays reasonable
Controlled loadThe queue absorbs bursts
Simpler codeYou write a task, not thread management

Trade-offs and cautions:

CautionDetail
Long or blocking tasksA task that waits for a long time ties up a pool thread. If all threads are blocked, new tasks wait too. (A form of starvation.)
Tasks that wait for other tasksIf every pool thread waits for a queued task that cannot start because no thread is free, you get pool deadlock
Shared data still needs locksA pool does not remove the race conditions of Section 8
Do not leave a task running when the program exitsCancel or finish work cleanly

9.3 Windows Thread Pool API

What it is: Windows includes a built-in pool for every process. Since Windows Vista it is the modern thread pool API, run by ntdll and the kernel. You do not have to build your own.

Main object types (all begin with Create...Threadpool...):

ObjectWhat it doesKey functions
WorkRun a function on a pool thread, as soon as a thread is freeCreateThreadpoolWork, SubmitThreadpoolWork, WaitForThreadpoolWorkCallbacks
TimerRun a function after a delay, or repeatedlyCreateThreadpoolTimer, SetThreadpoolTimer
WaitRun a function when a kernel object (event, semaphore...) becomes signalledCreateThreadpoolWait, SetThreadpoolWait
I/ORun a function when an overlapped I/O (asynchronous file or network operation) completesCreateThreadpoolIo, StartThreadpoolIo
Pool (custom)Your own private pool with your own limitsCreateThreadpool, SetThreadpoolThreadMinimum, SetThreadpoolThreadMaximum
Cleanup groupClose several objects togetherCreateThreadpoolCleanupGroup

Tiny example (shape only):

VOID CALLBACK MyTask(PTP_CALLBACK_INSTANCE inst, PVOID ctx, PTP_WORK work) {
    // runs on a pool thread
}
 
PTP_WORK w = CreateThreadpoolWork(MyTask, NULL, NULL);
SubmitThreadpoolWork(w);               // queue it
WaitForThreadpoolWorkCallbacks(w, FALSE);   // wait until it is done
CloseThreadpoolWork(w);

The old API. Before Vista there was QueueUserWorkItem. It still works, and uses the same pool underneath. Many programs still use it.

Higher-level wrappers you may see:

LanguagePool feature
C#/.NETThreadPool.QueueUserWorkItem, Task.Run, async/await (use the .NET pool)
C++std::async (may use a pool), Parallel Patterns Library (PPL)
PowerShellForEach-Object -Parallel (PowerShell 7) uses runspaces, not the Windows pool directly

How the pool sizes itself: Windows watches how busy the pool is. If tasks wait too long and threads are all busy, it can add threads gradually. If threads are idle for a while, it removes them. For CPU-heavy work, you should choose a size near the number of logical processors. For I/O-heavy work, a larger pool makes sense.

Rule of thumb:

Work typePool size idea
CPU-boundAbout the number of logical processors
I/O-boundLarger, since threads spend most time waiting; or use asynchronous I/O, which needs very few threads

See it: In Task Manager or Process Explorer, a busy program often shows several threads whose start address is in ntdll.dll (names like TppWorkerThread). These are the pool's worker threads. Programs such as browsers, antivirus and File Explorer have dozens.

Summary

TermPlain meaning
Thread poolA reusable group of worker threads fed by a task queue
Task / work itemA small job a pool thread runs
ReuseA thread finishes one task and picks up the next; it does not exit
WhyThread creation costs time and memory; too many threads hurt
Windows poolBuilt into every process; modern API from Vista on
Work / Timer / Wait / I/O objectsThe different kinds of jobs a pool can run
Custom poolYour own pool with your own min/max thread limits
QueueUserWorkItemThe older simple call that uses the same pool
Blocking taskTies up a pool thread; avoid long waits inside tasks