Parallelism

On this page 7

Parallelism is the simultaneous execution of multiple computations on separate hardware resources, such as CPU cores, processors, GPU units or machines, so that a workload finishes sooner or more work is done per second. In programming, it is a specific case within the broader topic of concurrency: concurrency is about structuring a program as tasks that can make progress independently, while parallelism is about those tasks actually running at the same instant.

A single-core machine can run a concurrent program by interleaving tasks, but it cannot run it in parallel. Since processor clock speeds stopped rising quickly in the mid-2000s, adding cores has become the main way hardware delivers more performance, which makes parallelism a core skill for performance-sensitive software.

How parallelism differs from concurrency

The distinction is often summarized as follows: concurrency is about dealing with many things at once, parallelism is about doing many things at once. A web server handling thousands of connections with asynchronous programming on one thread is highly concurrent but not parallel. A matrix multiplication split across 16 cores is parallel even if its structure is simple.

The following table contrasts parallelism with the neighboring concepts it is most often confused with.

ConceptCore ideaRequires multiple coresTypical goal
ParallelismTasks run at the same instantYesLower runtime, higher throughput
ConcurrencyTasks overlap in time, possibly interleavedNoResponsiveness, program structure
Asynchronous programmingNon-blocking waiting for I/ONoHandling many I/O operations efficiently
MultithreadingSeveral threads in one processNot necessarilyEither concurrency or parallelism
Parallelism compared with related concurrency concepts

Types of parallelism

Parallelism exists at several levels of the hardware and software stack, and the right level depends on how the work can be divided. Most performance-critical programs combine more than one.

FormWhat runs in parallelTypical example
Bit-level and instruction-levelBits and independent instructions inside one coreSuperscalar, out-of-order CPU pipelines
SIMD or vectorOne instruction on many data elementsAVX instructions processing 8 floats at once
Data parallelismThe same operation on different data partitionsImage filters, parallel map over arrays
Task parallelismDifferent operations at the same timeParsing, compressing and uploading in parallel
Pipeline parallelismStages of a process on different itemsVideo encoding stages, build pipelines
Distributed parallelismWork spread across machinesMapReduce, Spark, MPI clusters
Levels and forms of parallelism

GPUs push data parallelism to the extreme, running thousands of lightweight threads in lockstep groups. They excel at regular numerical workloads such as machine learning and graphics but perform poorly on branch-heavy, irregular code.

Limits on parallel speedup

Adding cores never gives unlimited speedup. Amdahl’s law states that if a fraction p of a program can run in parallel, the maximum speedup on N cores is 1 / ((1 − p) + p / N). With 95 percent parallel code, even an infinite number of cores yields at most a 20-fold speedup, because the serial 5 percent dominates.

Parallel fractionSpeedup on 8 coresSpeedup on 64 coresUpper limit
50%1.8×2.0×2×
90%4.7×8.8×10×
95%5.9×15.4×20×
99%7.5×39.3×100×
Maximum speedup predicted by Amdahl’s law

Gustafson’s law offers a more optimistic view: when the problem size grows with the number of cores, as in scientific simulations, the parallel part grows too and speedup scales better. In practice, speedup is further reduced by overheads: thread creation, synchronization, memory bandwidth limits, load imbalance and false sharing, where cores invalidate each other’s cache lines while writing to neighboring variables.

Programming models for parallel code

Writing raw threads is rarely the best starting point. Higher-level models divide the work and schedule it onto cores automatically, usually with a work-stealing thread pool sized to the number of hardware threads.

  • Parallel loops and collections: OpenMP pragmas in C and C++, Java parallel streams, .NET Parallel.For, Rust Rayon.
  • Fork-join: a task splits into subtasks, runs them in parallel and joins the results, which suits recursive divide-and-conquer algorithms.
  • Process pools: Python multiprocessing and similar tools run separate processes, sidestepping a global interpreter lock.
  • Message passing: MPI and actor systems exchange messages instead of sharing memory.
  • GPU kernels: CUDA, OpenCL and compute shaders express massively data-parallel work.

A key design choice is shared memory versus message passing. Shared memory is fast but requires locks or atomic operations; message passing avoids shared state at the cost of copying data.

Risks and common mistakes

Parallel code introduces nondeterminism: the order in which threads run changes from one execution to the next. Defects therefore appear intermittently and are hard to reproduce. The table pairs the classic hazards with their remedies.

HazardCauseRemedy
Race conditionUnsynchronized access to shared mutable dataLocks, atomics or immutable data
DeadlockThreads wait on each other’s locks in a cycleConsistent lock ordering, timeouts
Load imbalanceSome workers receive far more workSmaller chunks, work stealing
OversubscriptionMore busy threads than coresSize pools to the hardware
Excessive granularityTasks too small to cover scheduling costBatch work into larger units
Parallel programming hazards and remedies

The safest rule is to measure before and after parallelizing. Many workloads are limited by memory bandwidth, disk or network rather than CPU, and adding cores to such workloads brings little or no benefit.

The short version

Parallelism means several workers doing parts of a job at the same time. One cook preparing a five-course dinner alone takes hours; five cooks, each handling one course, finish much sooner.

There is a catch. If every course must pass through one shared oven, the cooks wait in line, and adding more cooks barely helps. Computers face the same limit: only the parts of a task that can truly be split gain speed from extra processor cores.

Where it makes a difference

A photo-processing service must generate thumbnails for 200,000 uploaded images every night. Run sequentially on one core, the job takes about six hours and sometimes overlaps with morning traffic.

The developers rewrite the job with a parallel map over a process pool sized to the server’s 16 cores. Each image is independent, so the work divides cleanly, with only the final index file written serially. The job now finishes in about 30 minutes, roughly a 12-fold speedup rather than 16-fold because of disk throughput and the serial step. That result matches what Amdahl’s law predicts and shows why measurement, not core count alone, sets expectations.