파이썬에서 multiprocessing을 활용해 프로세스 간 데이터를 공유할 때 Manager().dict()를 자주 사용한다.
하지만 공유 딕셔너리 내부에 있는 중첩된(Nested) 값을 직접 수정할 경우, 변경 사항이 다른 프로세스에 동기화되지 않는 이슈가 발생한다. 본 글에서는 이 현상의 원인과 해결 방법을 정리한다.
shared_config라는 공유 딕셔너리 내부에 ha_states라는 딕셔너리가 중첩되어 있다고 가정해 보자. 특정 상태가 변경될 때 값을 갱신하기 위해 아래와 같이 코드를 작성하는 경우가 많다.
def update_ha_status(self, src: str, is_active: bool):
# 공유 딕셔너리의 내부 뎁스(ha_states) 값을 직접 수정
self.shared_config["ha_states"][src] = "ACT" if is_active else "STD"
위 코드를 실행하면 에러는 발생하지 않지만, 다른 프로세스에서는 갱신된 ha_states 값을 읽지 못한다.
multiprocessing.Manager()가 반환하는 딕셔너리는 실제 파이썬 딕셔너리가 아니라, 상태 변경을 감지해 프로세스 간 전파를 담당하는 프록시(Proxy) 객체다.
이 프록시 객체의 핵심적인 특징은 자신의 최상위 키(Key)에 새로운 값이 명시적으로 할당(=)될 때만 변경을 감지한다는 점이다.
self.shared_config["ha_states"][src] = "ACT" 코드가 동작하는 과정을 단계별로 살펴보면 원인이 명확해진다.
self.shared_config["ha_states"]를 호출하여 값을 꺼낸다. 이때 반환되는 것은 더 이상 프록시 객체가 아니라 일반 파이썬 딕셔너리다.[src] 키에 "ACT"를 할당한다.shared_config 자체에는 어떠한 재할당 연산(=)도 발생하지 않았다. 따라서 프록시는 값의 변경을 알아채지 못하고 동기화도 수행하지 않는다.프록시 객체가 상태 변경을 정상적으로 감지하게 하려면, 내부 딕셔너리를 꺼내어 수정한 뒤 수정된 전체 객체를 최상위 키에 통째로 다시 할당해야 한다.
def update_ha_status(self, src: str, is_active: bool):
# 1. 기존 딕셔너리를 꺼내거나 새로 생성 (일반 딕셔너리 반환)
states = self.shared_config.get("ha_states", {})
# 2. 꺼내온 일반 딕셔너리의 값을 수정
states[src] = "ACT" if is_active else "STD"
# 3. 수정된 딕셔너리 전체를 최상위 키에 통째로 덮어쓰기 (재할당)
self.shared_config["ha_states"] = states
마지막 줄의 self.shared_config["ha_states"] = states 처럼 할당 연산자(=)를 사용해 값을 덮어씌우면, 프록시 객체가 이를 감지하여 모든 프로세스에 변경 사항을 즉시 동기화한다.
multiprocessing.Manager().dict() 등의 공유 메모리 객체를 사용할 때, 내부의 중첩된 자료형(딕셔너리, 리스트 등)을 갱신하려면 반드시 값을 꺼내어 수정한 뒤 통째로 재할당해야 한다. 멀티프로세싱 환경에서 흔히 겪을 수 있는 함정이므로 구조 설계 시 주의가 필요하다.