OS-04 Threads & Concurrency

Review

A thread is a basic unit of CPU utilization.

review

Thread Concept

DimensionProcessThread
Nature of the unitAn independent execution unit that can carry out tasks on its own and owns an independent set of resourcesA subset of a process; cannot exist apart from its process; one process may contain multiple threads
State informationHolds a large amount of state, such as the process ID, state, memory-management information, list of file descriptors, etc.Shares the process’s state, memory and other resources; maintains only a small amount of information such as the thread ID, program counter, register set and stack
Address spaceHas its own address space; the address spaces of different processes are isolated from each otherShares the address space of the process it belongs to
CommunicationRelies on inter-process communication (IPC) mechanisms such as pipes, message queues, shared memory, semaphores, etc.Besides IPC mechanisms, can also communicate directly through shared memory
Context switchAll state information must be saved and restored on a switch, involving a lot of data movement; expensive and relatively slowOnly a small amount of state needs to be saved and restored, and since the address space is shared there is no need to switch memory-management information; faster

multithreaded process

Pros:

  • Handling many similar tasks
  • Exploiting multicore systems
  • Thread creation is lightweight

Examples:

  • Client-server applications
  • Most operating system kernels are multithreaded

Multicore Programming

DimensionConcurrencyParallelism
Core definitionHandling multiple tasks “simultaneously” in a logical sense, achieved by switching between tasksExecuting multiple tasks simultaneously in a physical sense, relying on multiple cores or a distributed system
How it is achievedTime-slice round-robin (e.g. OS thread scheduling)
Multithreading, asynchronous I/O, event loops; a web server handling many requests (e.g. Nginx)
Directly allocating independent resources (e.g. multicore CPUs or distributed nodes)
Multithreading, multiprocessing, GPU/TPU acceleration, distributed computing; scientific computing (e.g. weather simulation), image processing (e.g. GPU rendering)
Resource requirementsLow; a single-core CPU sufficesHigh; requires a multicore CPU or a distributed system
Suitable task typesI/O-bound (e.g. database queries, network requests)Compute-bound (e.g. matrix operations, AI training)
Performance bottlenecksContext-switch overhead, I/O latencyTask decomposability, communication overhead (in distributed settings)
Programming complexityHigh; must deal with asynchronous logic and callback hellVery high; must deal with synchronization and distributed consistency
Real-world applicationsA browser rendering multiple tabs at once
A chat server handling a large volume of user messages
Video rendering software using GPU acceleration
A search engine crawling web pages in parallel across a cluster

Parallelism

  • Data Parallelism
    • Mapreduce
    • Distributed Machine Learning
  • Task Parallelism
    • Federated Submodel Learning
    • Distributed Machine Learning

Amdahl’s Law

Formula:

  • P: the proportion of the serial portion
  • N: the number of parallel parts

It is determined by the proportion of the serial portion

Multithreading Models

DimensionKernel ThreadsUser Threads
Core definitionManaged and scheduled directly by the OS kernel; visible to the kernel; assigned CPU time slices and resources directly; execute independentlyManaged by a user-space thread library; invisible to the kernel; treated as lightweight tasks within a single process; rely on kernel threads to execute
Managed byThe operating system kernelA user-space thread library (e.g. Pthreads, Java Threads)
Execution dependencyExecute independently, scheduled directly by the kernelDepend on kernel threads to execute (must be mapped onto kernel threads)
Scheduling controlThe kernel fully controls the scheduling policy (e.g. round-robin time slicing)The user thread library schedules on its own (e.g. cooperative or preemptive)
Context-switch overheadHigh (requires switching between kernel mode and user mode)Low (switches only within user space)
Multicore utilizationSupports true parallelism (each kernel thread runs independently)Limited by the number of kernel threads (must be mapped onto multiple cores)
Resource footprintEach thread has its own kernel stack (usually large)Share the process’s kernel stack; small user stacks (lightweight)
Effect of blockingOne thread blocking does not affect the othersIf mapped onto the same kernel thread, blocking suspends all user threads
Suitable scenariosCompute-intensive tasks, real-time systemsI/O-intensive tasks, high-concurrency scenarios
Typical implementationsNatively supported by operating systems such as Windows, Linux and macOSPthreads (POSIX), Java threads (early versions), Go’s Goroutines (hybrid model)
Mapping modelOne-to-one (1:1); each kernel thread executes independentlyMany-to-one (M:1): multiple user threads mapped onto one kernel thread (e.g. early Java threads); many-to-many (M:N): user threads dynamically mapped onto kernel threads (e.g. Linux’s NPTL, modern Java threads)
AdvantagesMulticore parallelism, strong robustness, no blocking riskLightweight, high concurrency, fast context switches
DisadvantagesHigh context-switch overhead, high resource usagePoor multicore utilization; blocking easily degrades performance
Plain-language analogyLike full-time employees in a factory: the factory manager (the kernel) assigns tasks directly, everyone works independently, and one person taking leave (blocking) does not affect the othersLike an outsourced team in a factory: managed by a foreman (the thread library) and sharing one set of equipment (a kernel thread); if one person takes leave (blocks), the whole team may come to a halt
Real-world examplesScientific computing (e.g. matrix operations), video rendering (using multicore GPUs)Web servers handling many concurrent requests (e.g. Node.js’s event loop), multitasking in games (e.g. animation plus network communication)

Thread models

Lightweight process (LWP): A mapping between user threads and kernel threads

Multithreading models

DimensionMany-to-one (M:1)One-to-one (1:1)Many-to-many (M:N)
MappingMultiple user threads mapped onto one kernel threadEach user thread mapped directly onto one kernel threadUser threads dynamically mapped onto multiple kernel threads
Kernel awarenessInvisible to the kernel; managed only by the user thread libraryThe kernel manages each thread directlyVisible to the kernel; user threads dynamically associated with kernel threads
Context-switch overheadLow (switches only within user space)High (requires switching between kernel mode and user mode)Medium (combines the characteristics of both)
Multicore utilizationCannot use multiple cores (limited to a single kernel thread)Makes full use of multiple cores (each thread runs independently)Uses multiple cores efficiently (kernel threads allocated dynamically)
Resource footprintLow (shared kernel stack)High (each thread has its own kernel stack)Medium (kernel threads allocated on demand)
Effect of blockingIf the kernel thread blocks, all user threads are suspendedOne thread blocking does not affect the othersWhen some threads block, the others can still run
Suitable scenariosI/O-intensive tasks (e.g. early Java threads)Compute-intensive tasks (e.g. scientific computing)High-concurrency scenarios (e.g. web servers, Go Goroutines)
Typical implementationsEarly Java threads (Solaris green threads), old-style LWPs (lightweight processes)Linux kernel threads (the clone system call), Windows threadsGNU’s NPTL (the native Linux thread library), Java 1.2+, Go Goroutines
AdvantagesLightweight, fast context switchesMulticore parallelism, no blocking riskEfficient multicore use, strong high-concurrency capability
DisadvantagesCannot use multiple cores, high blocking riskHigh resource usage, high context-switch overheadHigh implementation complexity, requires dynamic scheduling
Plain-language analogyOne chef (kernel thread) handles multiple orders (user threads) at once, but can only cook one dish at a time (everything pauses when blocked)Each order (user thread) is handled by its own chef (kernel thread); efficient, but more chefs must be hired (high resource consumption)Chefs (kernel threads) are assigned dynamically according to the number of orders (user threads): more chefs at peak times, fewer when idle (balancing resources and efficiency)
Real-world scenariosEarly Java web servers (lightweight concurrency)Video rendering, scientific computing (needs multicore parallelism)Modern web servers (e.g. Node.js, Go), distributed systems (e.g. Kafka)

Thread Libraries

Tools for manage threads

  • POSIX Pthreads
  • Java Threads
  • Win32 Threads

Pthreads

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <pthread.h> 
#include <stdio.h>
#include <stdlib.h>
int sum; /* this data is shared by the thread(s) */
void *runner(void *param); /* threads call this function */
int main(int argc, char *argv[])
{
pthread_t tid; /* create thread identifier */
pthread_attr_t attr; /* create thread attributes */
/* set the default attributes of the thread */
pthread_attr_init(&attr);
/* create the thread */
pthread_create(&tid, &attr, runner, argv[1]);
/* wait for the thread to exit */
pthread_join(tid, NULL);
printf("sum = %d ∖ n",sum);

Implicit Threading

  • Thread Pools
    • Create a number of threads in advance and have them wait for tasks to arrive
  • Fork-Join
    • Divide and conquer
  • OpenMP
    • A parallel programming interface
  • Grand Central Dispatch
    • Apple’s parallel programming framework
  • Intel Threading Building Blocks
    • Intel’s parallel programming framework

Threading Issues

Threading Issues

On Kernel and User

Having studied operating systems up to this point, I have noticed the concepts of Kernel and User appearing everywhere, so here are some of my own explorations and thoughts.

Origins

  • Multics (1960s)
    A time-sharing operating system developed jointly by MIT, Bell Labs and General Electric, which was the first to introduce a hierarchical protection mechanism (Protection Rings). The system is divided into multiple privilege levels (e.g. Ring 0 to Ring 3), where:

    • Kernel Mode: runs at the highest privilege level (e.g. Ring 0) and can access all hardware resources and system data.
    • User Mode: runs at a lower privilege level (e.g. Ring 3) with restricted access to sensitive resources.
  • UNIX’s inheritance and simplification
    When Ken Thompson and Dennis Ritchie at Bell Labs designed UNIX, they were inspired by Multics but simplified the protection mechanism, keeping only two modes:

    • Kernel mode: executes kernel code and operates the hardware directly.
    • User mode: runs application programs, which request kernel services through system calls.

Why separate user mode from kernel mode?

  • Security: prevents user programs from damaging the kernel, whether maliciously or by mistake.
  • Stability: a crashing user program does not affect the kernel.
  • Resource management: the kernel centrally schedules hardware resources (such as the CPU and memory).

Thoughts

The design philosophy behind kernel mode and user mode is a classic case of seizing the principal contradiction while setting aside the secondary ones. It also reflects strict hierarchical design.


Translated from the Chinese original.

Welcome to my other publishing channels

中文