Imagine four people counting four piles of ballots. Each person works independently, and one person combines the totals at the end. That is the fork/join model: split a problem into independent subtasks, process them concurrently, and combine their results into one solution. section 7.7
The pattern follows two main stages:
- Fork the data or problem into smaller chunks that can be processed independently. section 7.7
- Run the chunk-processing tasks concurrently, scaling horizontally when more processing resources are available. section 7.7
- Join the results by waiting for dependent tasks to finish and combining their outputs into the original solution. section 7.7
The independent work can run in parallel, but the final combination is sequential because it depends on every earlier result. The pattern is useful when data can be divided relatively independently; task number and task size still determine whether the decomposition is well matched to available processing resources. section 7.10 section 7.7
Counting election votes
Divide a large pile of votes into four smaller piles. Four workers count each pile, producing four candidate summaries. After all workers finish, combine those summaries to calculate the final count for every candidate. section 7.7
# Chapter 7/count_votes/count_votes_sequential.py
import typing as T
import random
Summary = T.Mapping[int, int]
def process_votes(pile: T.List[int]) -> Summary:
summary = {}
for vote in pile:
if vote in summary:
summary[vote] += 1
else:
summary[vote] = 1
return summary
if __name__ == "__main__":
num_candidates = 3 #1
num_voters = 100000 #1
pile = [random.randint(1, num_candidates) for _ in range(num_voters)] #1
counts = process_votes(pile)
print(f"Total number of votes: {counts}")
Use the Python vote-counting listing as a sequential baseline before comparing it with the concurrent fork/join version. section 7.7

