§ 1문제: 드론에 인자 전달
spawn_drone(func)는 func()를 인자 없이 호출한다. 그런데 각 드론은 자기 담당 열 번호를 알아야 한다. 어떻게?
틀린 방법 1: 공유 카운터 _FC[0] — 레이스 컨디션. 모든 드론이 동일한 시점에 _FC[0]를 읽어서 전부 col=0이 된다.
틀린 방법 2: global NEXT_COL — 게임 인터프리터에서 global 키워드가 작동하지 않는다.
틀린 방법 3: cc0()...cc15() 16개 하드코딩 — 동작은 하지만 유연성이 없다.
§ 2해결: 클로저 팩토리
정답은 클로저 팩토리다. 함수가 함수를 반환한다.
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가 구워진 함수. 각각 완전히 독립적인 함수 객체다.
함수 생성 시점에 값을 캡처(capture)한다. 실행 시점에 읽지 않는다. 따라서 레이스 컨디션이 발생하지 않는다. 이 패턴은 미로, 선인장, 해바라기 — 모든 드론 코드에 등장한다.
§ 3체인 워커 패턴
선인장 솔버에서 사용하는 특별한 패턴. 드론이 드론을 스폰한다.
chain_worker()는 spawn 시 자신의 인덱스를 가져가고, 다음 드론을 재귀적으로 스폰한다. K개 드론이 체인처럼 연결되어, 메인 드론이 index 0, 스폰된 드론이 index 1, 2, ... K-1을 담당한다.
_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) # 다음 드론 완료 대기
§ 4배치 스포닝
열이 32개인데 max_drones()가 16이면? 배치로 나눠서 스폰해야 한다. _batch() 헬퍼가 max_drones()씩 끊어서 스폰하고 wait_for로 동기화한다.
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