The Farmer Was Replaced— 코딩 게임 완전 해부
Chapter 03 · 기초

드론은 공장의 작업자다 — 클로저 팩토리와 체인 워커

spawn_drone은 인자 없는 함수만 받는다. 그런데 각 드론은 고유한 열 번호가 필요하다. global도 안 되고, lambda도 안 된다. 이 문제를 푸는 것이 클로저 팩토리 패턴이다.

Closure Factory Chain Worker Batch Spawning

§ 1문제: 드론에 인자 전달

spawn_drone(func)는 func()를 인자 없이 호출한다. 그런데 각 드론은 자기 담당 열 번호를 알아야 한다. 어떻게?

틀린 방법 1: 공유 카운터 _FC[0] — 레이스 컨디션. 모든 드론이 동일한 시점에 _FC[0]를 읽어서 전부 col=0이 된다.

틀린 방법 2: global NEXT_COL — 게임 인터프리터에서 global 키워드가 작동하지 않는다.

틀린 방법 3: cc0()...cc15() 16개 하드코딩 — 동작은 하지만 유연성이 없다.

§ 2해결: 클로저 팩토리

정답은 클로저 팩토리다. 함수가 함수를 반환한다.

클로저 팩토리 — _mk(col)
def _mk(col):
    def d():
        for i in range(col):
            move(East)       # 자기 열로 이동
        while True:
            for i in range(N):
                harvest()
                plant(ENTITY)
                move(North)
            move(East)
    return d

_mk(0)을 호출하면 col=0이 클로저에 "구워진(baked)" 함수가 반환된다. _mk(5)는 col=5가 구워진 함수. 각각 완전히 독립적인 함수 객체다.

클로저 팩토리 동작 원리 STEP 01 / 5
space 재생 · ← → 단계 · R 리셋
패턴클로저 팩토리 패턴

함수 생성 시점에 값을 캡처(capture)한다. 실행 시점에 읽지 않는다. 따라서 레이스 컨디션이 발생하지 않는다. 이 패턴은 미로, 선인장, 해바라기 — 모든 드론 코드에 등장한다.

§ 3체인 워커 패턴

선인장 솔버에서 사용하는 특별한 패턴. 드론이 드론을 스폰한다.

chain_worker()는 spawn 시 자신의 인덱스를 가져가고, 다음 드론을 재귀적으로 스폰한다. K개 드론이 체인처럼 연결되어, 메인 드론이 index 0, 스폰된 드론이 index 1, 2, ... K-1을 담당한다.

chain_worker() — 재귀 드론 스폰
_spawn_i = [0]       # mutable wrapper
_phase = [""]         # 현재 실행 단계

def chain_worker():
    me = _spawn_i[0]
    _spawn_i[0] = _spawn_i[0] + 1
    next_h = None
    if me < K - 1:
        next_h = spawn_drone(chain_worker)
    # 자기 작업 실행
    if _phase[0] == "plant": plant_rows(me)
    elif _phase[0] == "rows": sort_rows(me)
    # ...
    if next_h != None:
        wait_for(next_h)   # 다음 드론 완료 대기
직관 체인 워커는 "나와서 일하고, 내 뒤에 올 사람도 불러줘"라는 패턴이다. 각 드론이 자기 인덱스를 먼저 가져가고 그 다음 드론을 스폰한다. wait_for가 체인 끝에서부터 역순으로 풀리면서 모든 드론의 완료를 보장한다.

§ 4배치 스포닝

열이 32개인데 max_drones()가 16이면? 배치로 나눠서 스폰해야 한다. _batch() 헬퍼가 max_drones()씩 끊어서 스폰하고 wait_for로 동기화한다.

_batch(fns) — 배치 스포닝
def _batch(fns):
    n = max_drones()
    total = len(fns)
    batch = 0
    while batch < total:
        drones = []
        cnt = 0
        while cnt < n:
            idx = batch + cnt
            if idx >= total: break
            d = spawn_drone(fns[idx])
            if d != None: drones.append(d)
            cnt = cnt + 1
        for d in drones:
            wait_for(d)
        batch = batch + n