Skip to content
academia.sh

Course Intermediate

Concurrency

Published Updated

By the end of this course

Start course

01

Goroutines and Channels

Starting a lightweight concurrent unit of execution and stopping it are two separate jobs; a channel synchronizes at the same time it carries a value; where the buffer changes blocking behavior and where it does not; branch selection when waiting on more than one channel; and the information closing carries as a termination signal.

  1. 01 Goroutines Of twelve goroutines, only 2 stopped on their own; 5 stopped because they waited for a step some other goroutine took, 2 because a context was cancelled, and 3 never stopped at all: the course's binding distribution is produced in this lesson. Starting a goroutine is one line, and that line never says when it will stop — the decision to stop usually sits not in the goroutine's own body, but in a step taken by someone else. Bounding measurement: the "stopped on its own" class is also a contract, because a goroutine whose body has finished never announces this to anyone on its own.
  2. 02 Channels A channel does two jobs at once in the same line: it carries a value from one goroutine to another, and it forces the two goroutines to meet at the same point. A send on an unbuffered channel never completes until a receiver is ready on the other side; if there is never a receiver, the attempt never goes through. Bounding measurement: a channel is not a queue, because the value carried is copied, and any change the sender makes after the send never reaches the receiver.
  3. 03 Buffered and Unbuffered Channels A channel's capacity changes where blocking starts: a send passes without waiting at all until capacity fills, and once it fills, it blocks exactly like an unbuffered channel. How many of five attempted sends pass without blocking is measured at three capacities. Bounding measurement: the buffer does not remove synchronization, it only defers it — once a send to a buffered channel completes, the sender still does not know whether the receiver has taken that value.
  4. 04 select Statement The select statement is the language's answer to waiting on more than one channel at the same time: when only one branch is ready, that branch is chosen deterministically; when none are ready and there is a default branch, that branch is chosen. Source order carries no priority. Bounding measurement: when two branches are ready at the same time, selection is random, and this case is not run, only modeled — randomness is the language's own decision.
  5. 05 Channel Closing and Ranging Closing a channel is a termination signal: range ends on a closed channel, and a direct receive from a closed channel returns the zero value and false. The answer given by four forms of receiving (open, closed-full, closed-empty, closed-empty again) measures this. The responsibility to close always belongs to the sender. Bounding measurement: sending to a closed channel and closing a channel a second time both panic; both are caught with recover and written only as panic=true/false.

02

Concurrency Patterns

Distributing work with a fixed number of workers wired to job and result channels, a job being fanned out to many and the results gathered back, each stage's own closing responsibility in stream processing, context cancellation and carrying request-scoped values, and where timeouts and deadlines do and do not prevent a resource leak.

  1. 01 Worker Pools A fixed number of workers reads from a shared job channel; when the channel closes, all of them stop for the same reason — what they waited for arrived. When five jobs are distributed to eight workers, the minimum count of workers that stopped without ever taking a job is derived from the total jobs taken and the total reporting worker count. Limiting measurement: raising the worker count does not split the existing work, it only leaves more workers idle; in a job channel that is never closed, workers never stop at all.
  2. 02 Fan-Out and Fan-In When a source is fanned out to a fixed number of consumers (fan-out), the results merge back into a single fan-in channel (fan-in); the fan-in side's reason for stopping depends on a wait group closing the fan-in channel once all producers finish. Limiting measurement: the question of who closes the fan-in channel has exactly one answer — no producer can safely close it on its own (of three producers that try, two panic while one succeeds), and if it is never closed, the fan-in side never stops.
  3. 03 Pipelines In a pipeline arrangement, each stage opens its own output channel and closes it itself; five values passing through a generation stage, a squaring stage, and an add-one stage flow through a chain where no stage disturbs the order. Limiting measurement: when the consumer gives up early from the middle of the chain, the stages ahead of it never stop at all — even though every stage's closing responsibility is correctly set up, none of them knows when the consumer at the far end of the chain has given up. The fix is the subject of the next lesson.
  4. 04 The Context Package A single cancellation reaches all four goroutines waiting on ctx.Done() at the same time; all of them stop in the 'cancelled' class and ctx.Err() carries the same fixed reason. What a request-scoped value (context.WithValue) is for, and what it is not for, are measured separately. Limiting measurement: a context does not force anyone to stop, it only notifies — a goroutine that never checks ctx.Done() completes its own work to the end, even though the context has already been cancelled.
  5. 05 Timeouts and Deadlines A timeout is a branch choice in a select statement: if a result is already ready, the result branch is chosen; if the context is already cancelled (representing a timeout), the cancellation branch is chosen — both deterministic, because only one branch in the setup is actually ready. Duration is never measured anywhere, the only thing printed is which branch was chosen. Limiting measurement: a timeout rescues the waiting side but does not stop the awaited work — a work goroutine whose result no one will take anymore is left behind, as a leak, behind the caller who exited its branch.

03

Shared State

Mutual exclusion measured through lock scope, the counter contract of collective waiting, where once and atomic operations sit relative to the lock, and what the race condition detector does and does not find during testing.

  1. 01 Mutexes A goroutine blocks until the other goroutine holding the lock releases it, and its termination class is waited-for-arrived. The same counter is run through two setups, locked and unlocked, with 20 goroutines and 100 increments per goroutine, and both give 2000 — because the unlocked setup never sets up a real race, since each goroutine writes to its own cell. The limiting measurement models a single write that falls outside the lock's scope with an order forced by a channel: two increments are applied, but the result comes out as 1 instead of 101.
  2. 02 Wait Groups A counter waits for five workers to finish: Add raises the counter to five before they start, each worker guarantees its own Done with defer, and Wait returns once the counter reaches zero — the termination class is waited-for-arrived again. If the counter never reaches zero, Wait never returns, and this leak is measured with a boolean; when Done is called one time too many, the counter drops below zero and the wait group panics, the panic is caught with recover, and only panic=true is written. Limiting measurement: which of the five workers finishes first is never measured, only that all of them finish — the results are collected and sorted before being printed.
  3. 03 Concurrency Primitives Thirty goroutines call the same initialization, but the once contract guarantees the body runs exactly one time. Thirty goroutines, each with five hundred increments, increment the same counter once with atomic operations and once with a lock; both give 15000. The limiting measurement models, with an order forced through a channel, how two separate atomic variables (balance and transaction count) can each stay consistent on their own while producing an inconsistent pair when read together: the reading side sees balance=100, transactionCount=0.
  4. 04 Race Detector The detector is a member of the language's own distribution and is called from within the program: a fabricated source is written to a temporary directory and run with the detector option, and only the exit code is read. Of three setups, it finds the unprotected share, and does not find the locked share or the transfer through a channel. The limiting measurement — the course's final and most important boundary — shows that the same unprotected code, placed inside a branch that never runs, is also not found by the detector: the detector sees only the path that runs.

Final exam

Concurrency — Final Exam

In preparation

This course's question bank is not complete yet. The exam opens here automatically once enough questions are added.

Start typing to search.

↑↓ ↵ Esc navigate · open · close