The Big Picture: The Boss and the Helpers
When you open a program, Windows creates the process and one thread to start it. That first thread is the boss. As the program runs, the boss can hire helpers to do extra jobs. Helpers can also hire more helpers. When a job is done, the helper leaves.
Time ───────────────────────────────────────────────►
Main thread ████████████████████████████████████ (lives as long as the program runs)
Worker 1 ██████████ (download a file)
Worker 2 ████████████ (save to disk)
Worker 3 ██████ (scan for spelling)
2.1 The Main Thread (Created at Process Start)
What it is: The main thread (also called the primary thread) is the first thread of a process. Windows creates it at the same moment it creates the process, when you call CreateProcess (the Windows function that starts a program; see Chapter 9).
What the main thread does:
- Starts at the program's entry point (the first instruction the program runs). For a C or C++ program, the C runtime (a small helper library) runs first, then calls your
mainorWinMainfunction. - Runs your program's setup code.
- For a program with windows, it runs the message loop (see below).
- When it returns from
main, the C runtime callsExitProcess, which ends the whole process and all other threads.
This last point surprises many beginners. The main thread finishing normally means the program is over, even if worker threads are still busy.
The UI thread. In a program with windows (Notepad, a browser, File Explorer), the main thread is usually the UI thread. It owns the windows. It sits in a loop that does this over and over:
┌─────────────────────────────────────────────┐
│ 1. wait for a message (click, key, paint) │
│ 2. handle it (run the code for that button)│
│ 3. go back to step 1 │
└─────────────────────────────────────────────┘
This is the message loop. While the UI thread is busy with one long job, it cannot read the next message. Windows notices after about 5 seconds and shows "(Not Responding)" in the title bar. The fix is to move long jobs to a worker thread (next topic).
2.2 Worker Threads (Created During Execution)
What it is: A worker thread is any thread that a program starts after the main thread, usually to do one specific job in the background.
Common worker jobs:
| Job | Why a worker |
|---|---|
| Download or upload a file | Waiting on the network should not freeze the window |
| Read or write a big file | Disk is slow compared to the CPU |
| Heavy maths (video, zip, image filters) | Can be split across cores |
| Wait for incoming connections | A server needs to listen all the time |
| Autosave and spell-check | Quiet jobs in the background |
Rules of thumb:
- Worker threads share the heap with the main thread, so they can read the same data. But they must take care (Section 7).
- A worker should not touch windows directly. In Windows, only the thread that created a window may safely change it. A worker sends a message to the UI thread instead ("please update the progress bar").
- More threads is not always faster. If you have 8 logical processors, 50 busy threads just fight for them and add switching cost (Section 6).
Two kinds of work:
| Type | Meaning | Best number of threads |
|---|---|---|
| CPU-bound | Mostly calculating | About the number of logical processors |
| I/O-bound | Mostly waiting for disk or network | Can be many more, since most are asleep |
2.3 Thread Creation API
What it is: An API (Application Programming Interface) is a set of functions the OS offers to programs. Windows gives programs several ways to create a thread.
| Function | Level | Notes |
|---|---|---|
CreateThread | Win32 (the basic Windows API) | The standard call. You give it a function to run, and it returns a handle and a thread ID. |
_beginthreadex | C runtime | Use this in C/C++ programs. It sets up the runtime's per-thread data and then calls CreateThread. |
std::thread | C++ standard library | Portable. On Windows it uses the calls above. |
System.Threading.Thread, Task | .NET | Managed languages (C#). Task usually uses the thread pool (Section 9). |
CreateRemoteThread | Win32 | Starts a thread inside another process. Needs rights; used by debuggers and, sadly, by malware. |
| Thread pool functions | Win32 | The OS lends you a ready-made thread (Section 9). |
A simple C example (just to read, not to memorise):
DWORD WINAPI Worker(LPVOID param) {
// this code runs on the new thread
return 0; // thread ends with exit code 0
}
int main() {
DWORD tid;
HANDLE h = CreateThread(NULL, 0, Worker, NULL, 0, &tid);
WaitForSingleObject(h, INFINITE); // main waits for the worker to finish
CloseHandle(h); // give the handle back to Windows
return 0;
}What happens inside CreateThread (simplified):
1. Windows reserves memory for the new thread's STACK
(the default reserve is about 1 MB; only a small part is used at first).
2. It builds a kernel object that stores the thread's registers,
ID, priority and state.
3. It sets the instruction pointer to your function.
4. It puts the thread in the READY state (Section 4)
so the scheduler can run it soon.
Creating a thread is much lighter than a process, but it still costs thousands of CPU instructions plus memory. That is why programs that create lots of short-lived threads use a thread pool instead.
Parameters worth knowing:
| Parameter | Meaning |
|---|---|
| Stack size | How much stack to reserve. 0 means "use the default". |
| Creation flags | CREATE_SUSPENDED starts the thread paused, so you can set things up first |
| Thread ID | A number Windows gives back to identify the thread |
2.4 Thread Termination
What it is: A thread ends in one of these ways.
| Way | What happens | Good or bad? |
|---|---|---|
| Returns from its function | The thread ends and its exit code is saved | Best. Clean. |
ExitThread | The thread asks Windows to end it | Fine |
TerminateThread | Another thread kills it instantly | Dangerous. The victim gets no chance to clean up. It may hold a lock forever or leave memory half-changed. Avoid it. |
Process ends (ExitProcess, crash, or killed in Task Manager) | All threads end | Normal at the end of a program |
Safe way to stop a worker: use a stop signal. The worker checks a flag or an event (Section 7) every so often. When the flag is set, it finishes its current step, cleans up and returns.
main thread: "please stop" ──► sets stop-event
worker: ... work ... check event ... work ... check event → set!
──► clean up ──► return ✔ safe
After a thread ends:
- Its stack memory is released.
- Its kernel object stays until every handle to it is closed (call
CloseHandle). This is how another thread can still read the exit code withGetExitCodeThread. - A thread that is waiting on this one (
WaitForSingleObject(h, ...)) is woken up. - The thread state becomes Terminated (Section 4).
Handle leaks: If a program creates threads but never closes the handles, the kernel objects pile up. Task Manager's Handles column slowly grows. This is a classic bug.
2.5 Thread Count Limits
A process cannot have unlimited threads. Each one needs a stack (reserved address space) and kernel memory. A 32-bit process has only 2 GB of address space by default, so thousands of threads with 1 MB stacks can run it out. A 64-bit process has far more room, but each thread still costs memory and switching time. The real limit is almost always good design, not the OS.
Summary
| Term | Plain meaning |
|---|---|
| Main (primary) thread | The first thread, created with the process |
| UI thread | The main thread of a window program; runs the message loop |
| "Not Responding" | The UI thread was too busy to read messages |
| Worker thread | A helper thread started later for one job |
CreateThread | The basic Windows call that makes a thread |
_beginthreadex | The C/C++ way; wraps CreateThread |
CREATE_SUSPENDED | Start the thread paused |
Return / ExitThread | Clean ways to end a thread |
TerminateThread | Forced kill. Dangerous. Avoid. |
| Stop signal | A flag or event that asks a worker to finish politely |