liveBook · Manning

Thread safety

As explained in Functional Design and Architecture: Examples in Haskell

Thread safety is the property that shared code or data remains correct when accessed concurrently by multiple threads.

Thread safety keeps shared state usable when several threads access it at once. Plain mutable references make each read and write possible, but leave coordination to the developer; competing operations can become nondeterministic or race. Atomic thread-safe variables improve the primitive operation, yet application-level correctness still depends on how reads, writes, and resource use are composed. section 7.2.4 section 9.1.2

A reporting channel that loses events

A meteor-reporting channel stored as an IORef list is updated by reading the list and writing back a new one. An astronomer thread reports meteors while a tracking-center thread reads the list and clears it. Those separate steps can interleave: reports may be lost, processed twice, or outpace consumption, with memory use growing as a result. section 9.1.2 section 9.1.2

Choose synchronization at the shape of the state. An MVar provides atomic reading and writing, but does not by itself eliminate race conditions, deadlocks, or nondeterminism across a larger protocol. Software transactional memory (STM) instead performs mutations in isolated transactions; competing transactions can be delayed or retried, and transactional operations can be combined. section 7.2.4 section 7.2.4

Figure 9.3 Race condition in accessing a shared resource and double-usage error

Trace the interleaving between reporting and clearing the shared channel to see why individually simple IORef operations do not make the workflow safe. section 9.1.2

Thread safety also requires controlling which concurrent actions business logic may express. A layered design can expose operations for shared state, delays, and random values while reserving process creation for a top-level application layer. Restricting child processes from forking further threads makes unbounded thread creation impossible to represent and keeps resource-pool limits enforceable. section 9.1.3 section 9.1.3

Granularity changes the concurrency trade-off. A simulator runtime can use an outer MVar map whose values are themselves MVars, allowing independent keys to be updated without replacing the entire map. A coarse-grained MVar around a whole map instead requires repeated whole-map updates and may be less efficient when the same key changes often. section 8.2.2 section 8.2.2

How Functional Design and Architecture: Examples in Haskell covers it

How deep it goes
Explains races, atomic variables, STM transactions, layered process control, and state granularity without detailing STM properties.
Examples
Uses a meteor-reporting channel, restricted process language, and nested-MVar key-value simulator state.
What it assumes
Builds from prior exposure to threads, actors, IORefs, and interpretable languages.
What you can do after
Evaluate/understand where shared-state designs can race, overload resources, or benefit from transactional or fine-grained coordination.
How it connects
Connects IORef, MVar, software transactional memory, race conditions, deadlocks, resource pools, and coarse-grained versus fine-grained state.

Also explained in

Compare how 8 books explain thread safety →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Functional Design and Architecture: Examples in Haskell. The section links point to the parts of the book it draws on.