The Kitchen Sync
A narrative exploration of synchronization primitives

Once upon a time there was a special restaurant, The Kitchen Sync. It wasn’t famous for it’s food: it was famous for having a wider range of synchonization primitives than any other restaurant in town.

1 Barriers

The restaurant only sat people in groups of 4. You’d show up and be placed in the entrance waiting room; once enough people were there, 4 at random (not necessarily a group who arrived together) would be picked to enter the restaraunt.

from threading import Barrier
entrance = Barrier(4)

def arrive():
    entrance.wait() # stop waiting in groups of 4
    dine()

2 Locks

Drinks were self-serve, but there was only one drinks station. If you wanted a refill on your water you might get lucky and get there when no one else was there, or you might have to wait for someone else to finish at the station first.

from threading import Lock
drink_station = Lock()

def get_drink():
    with drink_station: # will wait here
        refill_drink()
    return # sit back down

3 Condition variables with notify_all

Food was also self-serve single-station, but in addition it often ran out. You’d wait your turn in a queue to get access to the station, but when you got there there the dish you wanted might be empty. When that happened you’d go into the food waiing room. Eventually a server would add more food and notify everyone in the waiting room to get back into the queue to access the station again.

from threading import Condition
food_station = Condition()

dishes = {"bread":0, "salad":0, "fruit":0, "protein":0}

def get_food(dish):
    with food_station: # will queue here
        while dishes[dish] == 0:
            food_station.wait() # enter waiting room here
            # wait() also is where you re-queue when notified
        dishes[dish] -= 1
        add_to_plate(dish)
    return # sit back down

def refill_food(dish, servings):
    with food_station:
        dishes[dish] += servings
        food_station.notify_all() # reqeue waiting customers
    return # got back to kitchen

4 Condition variables with notify

When you finished your meal you’d wait in line at the register to pay, and after you paid you’d be given a desert. Deserts were made one at a time and if there weren’t enough deserts, after paying you’d be put in the desert waiting room. When a new desert was ready one person in the waiting room would called to get their desert, but sometimes they’d come back to the waiting room a moment later because someone else had arrived, paid, and taken that desert while they were leaving the waiting room.

from threading import Condition
register = Condition()

deserts = 0

def end_meal():
    with register:
        pay()
        while deserts == 0:
            register.wait()
        deserts -= 1
    return # leave restaurant with desert

def make_desert():
    with register:
        deserts += 1
        register.notify() # requeue 1 waiting customer

5 Lock-free queues

Once a year the restaurant had a special eat-for-free day as a marketing strategy. On those days at the end of your meal you lined up to each get a custom desert.

from queue import SimpleQueue

desert_line = SimpleQueue()

def special_end_meal():
    desert = desert_line.get() # waits if none are ready
    return # leave with desert

def special_make_desert():
    desert_line.put(custom_desert())

6 Manual events

The restaurant was on a very busy road were it was not safe to leave on your own. Instead, an attendant would periodically stop traffic and let out anyone waiting to leave. Everyone has to wait: new departures while traffic is stopped are not let go until the next time traffic is stopped. Well, almost everyone; there was a tiny window of time when you can show up between the attendant letting people out and the attendant blocking any new arrivals, but you have t get your timing exactly right to hit that time window.

from threading import Event
may_leave = Event()

def leave():
    may_leave.wait()
    depart()

def stop_traffic():
    may_leave.set() # all waiters stop waiting
    # race condition at this time may_leave.wait() doesn't wait
    may_leave.clear() # new arrivals must wait