Thread (computing) (source code)

= Thread
{disambiguate=computing}
{wiki}

= Software thread
{synonym}

A <software thread> is an execution stream within a <operating-system process>. Each <software thread> has its own <execution context>, including <processor registers>, an <instruction pointer>, and a <call stack> selected by its <stack pointer>. <Software threads> in the same <operating-system process> share its <address space>, global variables, dynamically allocated storage (the memory heap), and resources such as <file descriptors>. Separate register states let two <software threads> pause at different instructions and later resume correctly. A <context switch> saves and restores those states; it does not copy the entire shared <address space>. https://pages.cs.wisc.edu/~remzi/OSTEP/threads-intro.pdf[Arpaci-Dusseau and Arpaci-Dusseau, OSTEP chapter 26, introduction and figure 26.1: thread contexts and shared address space]{ref}https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap03.html#tag_03_189[The Open Group, POSIX.1-2024, sections 3.189–3.190: Live Process and Live Thread, shared resources and addressable storage]{ref}

In the right half of the diagram under <operating-system process>, T1 and T2 access the same counter `x` at <virtual address> `0x1234`. Their <call stacks> occupy separate regions of the same <address space>, and their saved <processor registers> can contain different values. A <software thread>'s own <call stack> means the stack used for its calls, not a hardware protection boundary against another <software thread>. Another <software thread> with a suitable address can access that stack's live data, subject to the program's lifetime and <synchronization> rules. Sharing an <address space> enables easy communication and makes accidental interference possible.

Concurrency means that several activities can make progress over overlapping intervals; parallel execution means that several activities execute at the same instant. On one <CPU> execution slot, <process scheduling> can interleave <software threads> without simultaneous execution. Multiple processor cores can run eligible <software threads> in parallel, but merely creating <software threads> does not ensure parallel execution or a speedup.

Consider an explicitly abstract interleaving model: shared `x` starts at $0$, and each of T1 and T2 performs three indivisible logical steps in its own order: load `x` into its own register `r`, add $1$ to `r`, then store `r` into `x`. Each load sees the latest preceding store in the displayed order. These model assumptions specify the example; they do not describe every programming language or every hardware memory model. In serial order T1 finishes all three steps before T2 starts, so T2 loads $1$ and stores $2$.

An overlapping order instead gives T1 load $0$, T2 load $0$, T1 add to get `r1 = 1`, T2 add to get `r2 = 1`, T1 store $1$, and T2 store $1$. The load/store subsequence is therefore T1 load $0$, T2 load $0$, T1 store $1$, T2 store $1$: final $x=1$, although each <software thread> incremented its register correctly. The second store replaces the first with the same value, losing one intended update. This <race condition> is possible even with interleaving on a single <CPU>.

\Image[/thread-lost-update-interleaving.png]
{title=Two abstract load/add/store increments retain separate registers but can leave the shared counter at $1$ instead of the serial result $2$}

This is not an unprotected C implementation example. Conflicting unsynchronized accesses to an ordinary non-atomic C object from different <software threads> can constitute a data race, which has undefined behavior; the abstract trace is not a prediction of that program's allowed outcomes. https://www.open-std.org/jtc1/sc22/wg14/www/docs/n1570.pdf[ISO/IEC JTC1/SC22/WG14, committee draft N1570, 12 April 2011, section 5.1.2.4 paragraph 25: data races and undefined behavior]{ref}

The repair explains why <synchronization> matters: <mutual exclusion> around the whole load/add/store sequence prevents the other <software thread> from starting an increment halfway through it. Alternatively, an appropriate <atomic operation> can make the complete increment one indivisible read-modify-write. In the toy model, either repair makes the two increments take effect in an order, producing $0+1+1=2$. Making only each load and each store an <atomic operation> still permits the displayed lost update. https://pages.cs.wisc.edu/~remzi/OSTEP/threads-locks.pdf[Arpaci-Dusseau and Arpaci-Dusseau, OSTEP chapter 28, sections 28.1–28.2 and 28.11: locks around updates and atomic fetch-and-add]{ref}

The attraction is independently progressing activities with direct access to shared data: a user-interface <software thread> can handle input while another waits for <input-output>, and workers can divide independent pieces of a large calculation among available cores. The first benefit can exist on one <CPU>; the second requires suitable work and execution resources. <Context switches>, <synchronization>, register-state records and extra <call stacks> consume time or <computer memory>, so the useful partition depends on the workload. https://pages.cs.wisc.edu/~remzi/OSTEP/threads-intro.pdf[Arpaci-Dusseau and Arpaci-Dusseau, OSTEP chapter 26, section 26.1: parallel work and overlapping input-output with other activity]{ref}