liveBook · Manning

Locking

As explained in Grokking Concurrency

A lock is a synchronization mechanism that restricts access to a shared resource so that competing tasks cannot modify it simultaneously.

Imagine putting a “Do not disturb” sign on a printer while it prints a file. Other tasks wait rather than mixing their lines into the same printout. A lock serves that purpose for a shared resource: a task acquires it before work begins and releases it when the work is done. section 8.3 section 8.4

The code that uses the shared resource is a critical section. Without protection, another task may enter while an operation is unfinished, causing a race condition. The operating-system scheduler cannot infer every application-specific rule, so the tasks must coordinate access. section 8.3 section 8.7

A mutex is a lock that gives one task exclusive access at a time:

  • Acquire the mutex before entering the critical section. If another task holds it, wait until it becomes available. section 8.4
  • Use the shared resource, then release the mutex so another task can proceed. section 8.4
# Chapter 8/race_condition/synced_bank_account.py
from threading import Lock
from unsynced_bank_account import UnsyncedBankAccount

class SyncedBankAccount(UnsyncedBankAccount):
    def __init__(self, balance: float = 0):
        super().__init__(balance)
        self.mutex = Lock()

    def deposit(self, amount: float) -> None:
        self.mutex.acquire()                            #1
        super().deposit(amount)
        self.mutex.release()                            #2

    def withdraw(self, amount: float) -> None:
        self.mutex.acquire()                            #1
        super().withdraw(amount)
        self.mutex.release()                            #2

The Python account uses one threading.Lock around both deposit and withdrawal operations. Each operation acquires the mutex before changing the balance and releases it afterward, preventing concurrent transactions from interfering with one another. section 8.4

When one lock is not enough

In the dining-philosophers problem, each diner needs two chopsticks, each protected by a lock. If every diner holds one chopstick while waiting for the other, none can continue. Locks protect individual resources, but an unsafe pattern of acquiring several locks can stop progress. section 9.1

Locks also have a cost: acquiring and coordinating them adds work, and contention adds delays. An unreleased lock can leave other tasks waiting. When practical, avoid shared resources or use message passing; when sharing is necessary, protect the critical sections deliberately. section 9.1 section 9.10

How Grokking Concurrency covers it

How deep it goes
Defines mutex states and acquire-and-release behavior, then demonstrates their use in Python; leaves processor-level synchronization instructions abstracted away.
Examples
Uses a printer, concurrent bank-account transactions, and dining philosophers to show exclusive access and competing-lock hazards.
What it assumes
Introduces critical sections and mutexes from everyday examples; assumes enough Python familiarity to follow a threading.Lock example.
What you can do after
Apply it via Python threading.Lock to protect shared account operations and recognize when competing locks can prevent progress.
How it connects
Connects critical sections and race conditions to mutexes, semaphores, deadlocks, atomic operations, and message passing.

Also explained in

Compare how 18 books explain locking →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Grokking Concurrency. The section links point to the parts of the book it draws on.