A thread is another point of execution inside a single process: its own program counter, its own registers and its own stack, but the same address space as every other thread in the program. The tabs below follow OSTEP Chapter 26, from what a second stack looks like to why a shared counter ends up with the wrong value.
A thread's stack
what has to be private when everything else is shared
The address space
A 16KB address space, drawn as in the chapter. Code and heap are shared by every thread; each stack belongs to one thread alone.
shared
thread 1 only
thread 2 only
thread 3 only
How many threads
Every thread the program creates needs a stack of its own somewhere in the space.
Private and shared
The OS keeps one thread control block per thread, much as it keeps a process control block per process. Switching between threads of the same process leaves the address space, and so the page table, untouched.
Keyboard: ←→ to step, Space to play or pause.
A context switch between threads
save into one TCB, restore from the other
The processor and the two control blocks
One set of registers, two threads that each need their own copy of it.
CPU registers
↕the OS moves register state between the CPU and the TCBs
Where the stack pointer is pointing
Each thread runs on its own stack, so the switch changes the stack pointer as well as the program counter.
Thread creation and execution paths
the same program, three legal orderings
The trace
Ordering
Only the ordering differs; the program is identical in all three. Which one happens is up to the scheduler, and it can differ from run to run on the same machine.
The line counter = counter + 1 compiles into three instructions, and a thread can be interrupted between any two of them.
Interrupt thread 1
Thread 1
Thread 2
Shared memory
One counter at address 0x8049a1c, reachable by both threads because they share the address space.
counter =50
The words for it
A critical section is a piece of code that touches a shared resource, here the three instructions that update the counter.
A race condition is when the result depends on when the scheduler happens to switch. The outcome is indeterminate: correct on most runs, wrong on some.
The fix is mutual exclusion, a guarantee that only one thread is inside the critical section at a time, so the update becomes atomic. That is what locks are for, and is where the next chapters go.
Program, traces and the counter example follow OSTEP, Chapter 26, Concurrency: An Introduction.