C++ / a working model

130 / 163   ·   C11   ·   8 min

Concurrency: An Introduction

Keep this sentence

This chapter presents threads from a fresh angle: a single process may contain several independent flows of execution that all share one address space. Every thread carries its own program counter and registers, so a switch among them leaves the page table untouched. Each thread also owns a private stack. Threads exist mainly so that multiple cores can work in true parallel and so that a program can keep making progress while some of its threads wait for I/O.

In this lesson
  1. What a Thread Really Is
  2. Why Bother with Threads
  3. Multiple Stacks inside One Address Space
  4. Example
  5. Exercise

Official chapter PDF

What a Thread Really Is

Think of a thread as a lightweight flow of execution living inside a process. It owns a private program counter and a full register set, so the scheduler can bounce among threads much as it bounces among processes. The crucial difference is that every thread of the same process sees exactly the same virtual memory; global variables and heap objects are therefore immediately visible to all of them.

Why Bother with Threads

Contemporary machines contain several processor cores. Chop a large computation into pieces and hand each piece to a thread; those pieces can then run truly simultaneously. A second important reason is that when one thread stalls on disk or network I/O, the remaining threads of the same process can still occupy the CPU and keep the whole program moving.

Multiple Stacks inside One Address Space

A classic single-threaded process owns just one stack, normally sitting near the bottom of the address space. Once several threads exist, each must have its own stack that stores locals, arguments and return addresses. Those stacks occupy distinct corners of the shared virtual address space, while the heap remains unique and is usable by every thread.

Pitfalls

  • Assuming threads always run in the exact order they were created
  • Reading and writing shared variables before the later chapters introduce synchronization

Run an example

Minimum C11 · complete program · Download .c

#include <stdio.h>
#include <pthread.h>

int results[2] = {0, 0};

void *thread_one(void *unused) { (void)unused;
    results[0] = 10 + 20;
    return NULL;
}

void *thread_two(void *unused) { (void)unused;
    results[1] = 30 + 40;
    return NULL;
}

int main(void) {
    pthread_t t1, t2;
    printf("main: begin\n");
    pthread_create(&t1, NULL, thread_one, NULL);
    pthread_create(&t2, NULL, thread_two, NULL);
    pthread_join(t1, NULL);
    pthread_join(t2, NULL);
    printf("result0=%d result1=%d\n", results[0], results[1]);
    printf("main: end\n");
    return 0;
}

Compile locally

gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -pthread ostep-26-threads.c -o example && ./example

Expected result

main: begin
result0=30 result1=70
main: end

CHECK YOUR UNDERSTANDING

Close the answer. Explain it.

Why does a context switch among threads skip the page-table change that a process switch performs?

Show a reference answer

Because all threads of one process already share the identical address space, the hardware page-table register can stay unchanged.

Check the sources

Drafts and official chapters change. The version mark is only the example’s minimum.

Back to the catalog