What a Thread Is
Chapter Twenty-Six
Syllabus topic Module 1, "Processes - Threads - Overview"
Pages 101 to 104 of 452
In one line
A thread is one flow of execution inside a process, and a process may have many of them, all sharing its memory.
The form to write: a thread is the basic unit of processor utilisation. It has a thread id, a program counter, a register set and a stack of its own, and it shares its code, its data and its operating system resources with the other threads of the same process.
Why threads exist at all
Chapter twenty two showed two processes sharing memory, and it took four system calls and a great deal of care. Now ask the opposite question: what if two flows of execution wanted to share everything, all the time, by default?
That is a thread. And the reason to want it is that most real programs have several things to do at once on the same data:
- a browser drawing a page while downloading the next image into the same document;
- a word processor checking spelling while you type into the same text;
- a web server answering two hundred requests out of the same cache.
Doing each of those with separate processes means copying or sharing the data explicitly. Doing them with threads means they are simply looking at the same variables.
What is shared and what is private
This is the table the topic reduces to and it is asked directly.
| Shared by every thread of a process | Private to each thread |
|---|---|
| the code, the text section | the program counter |
| the data section, the global variables | the registers |
| the heap | the stack |
| open files and descriptors | the thread id |
| the current directory, the user | its signal mask, its errno |
| the process id | its own place in a queue |
Every thread has its own stack, and that is not optional. A stack holds the frames of the function calls in progress, and two threads are in different functions at the same moment, so they cannot share one. The stack is what makes a thread a thread.
A global variable is shared and a local variable is not, and that one sentence is where half the marks in this whole row are. A local variable lives on the thread's own stack; a global lives in the data section, which is shared. Chapter thirty four's race condition is exactly a shared global being changed by two threads, and Chapter thirty seven's fix is a lock around it.
The four benefits
MU's text book gives four and an examination asks for them by name.
- Responsiveness. A program can keep answering the user while part of it is busy or blocked.
A browser whose download thread is waiting for the network still scrolls.
What a Thread Is
- Resource sharing. Threads share memory by default, with no system call and no setup. Two
processes must ask for shared memory; two threads already have it.
- Economy. Making a thread is far cheaper than making a process, and switching between two
threads of one process is cheaper than switching between two processes. Chapter sixteen's worked example says why: the memory mapping does not change.
- Scalability, also called utilisation of multiprocessor architectures. A process with
one thread can use one processor however many the machine has. Threads can run on all of them at once.
The fourth is the one that has changed in importance. When the four were first written, machines with more than one processor were rare. Every phone now has six or eight cores, and a single threaded program uses one of them.
Seeing threads on a real machine
A thread is not a process, but Linux shows both in the same table if you ask.
$ ps -o pid,nlwp,comm -p $$ --no-headers
9 1 bash
$ grep Threads /proc/self/status
Threads: 1
$ ls -d /proc/$$/task/*
/proc/9/task/9nlwp is the number of light weight processes, which is what Linux calls a thread, and Threads in the status file says the same thing. Each one has a directory of its own under task. A program with one thread has one of each, and Chapter twenty seven's program has ten.
Worked example: one job, three designs
A program must read a hundred files and count the words in each.
One process, one thread. Open a file, read it, count, next. While the disk fetches a block the program does nothing at all. On a two processor machine it uses one processor, and badly.
A hundred processes. Each counts one file. They genuinely run at once and use every processor. But each costs a full process creation, each has its own address space, and collecting the hundred answers needs inter-process communication: shared memory or a pipe per child, and Chapter twenty two or twenty three of work.
One process, several threads. Each thread takes a file from a shared list and adds its count to a shared total. They run at once, they use every processor, creation is cheap, and the answers are simply added into a shared variable with no communication machinery at all.
The third design is best and it is also the one that can go wrong in a way the other two cannot. "Adds its count to a shared total" is a race condition (Chapter thirty four) unless the addition is protected (Chapter thirty seven). Threads buy sharing and sell safety, and the whole of MU's process synchronisation row is the price.
What a Thread Is
Distinctions that carry marks
| Process | Thread | |
|---|---|---|
| Address space | its own | shared with its siblings |
| Creation cost | high: a whole address space | low |
| Switching cost | high: the memory mapping changes | low: it does not |
| Communication | needs shared memory or messages | ordinary variables |
| Protection between them | full: one cannot touch the other's memory | none at all |
| If one crashes badly | the others survive | the whole process usually dies |
| Also called | a heavyweight process | a lightweight process |
| Its own stack | The shared heap | |
|---|---|---|
| Holds | local variables, call frames, the return address | anything malloc gave out |
| Visible to other threads | no, in practice | yes |
| Grows | per thread | for the whole process |
What it does not mean
Threads do not make a program faster by themselves. They let it use more processors and stop waiting. A program that is limited by the disk gets nothing from more threads.
A thread is not a lightweight process in the sense of being a small process. It has no address space of its own at all. Linux calls it a light weight process because it implements both with one mechanism.
Threads do not remove the need for synchronisation. They create it. Two processes are protected from each other by the hardware. Two threads are protected from each other by nothing.
A single threaded program is not obsolete. It is simpler, it cannot race, and for most tasks it is the right answer.
Quick revision
- A thread is the basic unit of processor utilisation: a thread id, a program counter,
registers and a stack of its own, sharing code, data, the heap and open files with its siblings.
- Shared: text, data, globals, heap, open files, the process id. Private: program
counter, registers, stack, thread id.
- A global variable is shared; a local variable is on the thread's own stack and is not.
- Four benefits: responsiveness, resource sharing, economy, scalability on
several processors.
- Threads are cheaper to make and cheaper to switch between than processes, because the address
space does not change.
- Linux calls a thread a light weight process;
ps -o nlwp,/proc/<pid>/statusand
/proc/<pid>/task all count them.
- Threads buy sharing and sell safety: everything in MU's synchronisation row is the price.
Test yourself
- Define a thread. The basic unit of processor utilisation, with its own thread id, program
counter, registers and stack, sharing its code, data and operating system resources with the other threads of the same process.
- What does a thread have of its own, and what does it share? Its own program counter,
registers, stack and thread id. It shares the text, the data section and globals, the heap, the open files and the process id.
- Why must each thread have its own stack? Because the stack holds the frames of the calls
What a Thread Is
in progress, and two threads are inside different functions at the same moment.
- Name the four benefits of threads. Responsiveness, resource sharing, economy, and
scalability on a machine with several processors.
- Why is switching between two threads cheaper than between two processes? The address space
does not change, so the memory management hardware and its translation cache are untouched. 6. A program counts words in a hundred files using ten threads that add to one total. What is the danger? The shared total is changed by ten threads at once, which is a race condition. The addition must be protected by a lock.
- Is a local variable shared between threads? No. It lives on the calling thread's own
stack. A global variable is shared, because it lives in the shared data section.
The rest of this subject
These notes are cut from the University's printed syllabus. Open the syllabus itself, or the past papers, for the same subject.