본문으로 건너뛰기

self._lock 이 잠그는 것은 무엇인가

변경 검증 가이드 (다음 fetch 후):

git log eaa2bfee..HEAD -- lmcache/v1/storage_backend/raw_block/core.py lmcache/v1/distributed/l2_adapters/raw_block_l2_adapter.py

위에 커밋이 잡히면 (1) self._lock 이 RLock/asyncio.Lock 등으로 바뀌었는지, (2) ThreadPool 구성(store 2 / lookup 1 / load 4) 이 달라졌는지 확인. (1) 이면 "출입증 비유" 가 흔들리고, (2) 면 "누가 호출하는가 (MP 모드)" 다이어그램의 워커 개수를 갱신해야 함.


결론부터

파이썬의 threading.Lock 은:

  • 스레드를 잠그지도 않고
  • 변수/객체를 잠그지도 않음
  • "한 번에 한 스레드만 통과" 시키는 출입증 같은 거
with self._lock:
# 이 블록 안에는 한 번에 한 스레드만 들어올 수 있음
...

블록 자체에 들어가는 걸 직렬화할 뿐. 블록 바깥의 다른 코드는 영향 안 받음.

"_index 를 보호한다" 는 무슨 뜻

이건 개발자들의 약속(컨벤션) 이지 코드에 강제되는 게 아님.

"_index 를 만지는 모든 코드는 반드시 with self._lock: 안에서 만지자"

이 약속을 모두가 지키면 결과적으로:

  • A 스레드가 _index 수정 중이면
  • B 스레드는 같은 with self._lock: 블록에서 대기
  • _index 가 반쯤 수정된 상태로 읽히는 일이 없음

락 자체가 _index 를 잠그는 게 아니라, "_index 만지는 코드를 한 줄로 세우는" 도구.

화장실 열쇠 비유

[직원 휴게실]
화장실 ──── 열쇠 1 개 (self._lock)
냉장고 ──── 자유 출입
  • 화장실 열쇠는 화장실 문을 잠그는 게 아니라 그냥 열쇠 1 개
  • 모두가 "화장실 들어갈 땐 열쇠를 받자" 라는 규칙을 지키니까 결과적으로 한 번에 한 명
  • 규칙을 어기고 열쇠 없이 들어가면? → 락은 막을 방법 없음

_index 보호는 모든 코드가 with self._lock: 컨벤션을 지킨다는 전제에서 성립.

"Core 가 스레드로 돈다" 는 오해

RawBlockCore 객체에 전용 스레드가 따로 있는 게 아님.

대신, 여러 스레드가 같은 RawBlockCore 인스턴스를 동시에 호출.

누가 호출하는가 (MP 모드)

RawBlockL2Adapter
├── store ThreadPool (worker 2 개) ──┐
├── lookup ThreadPool (worker 1 개) ──┼──> 같은 RawBlockCore 인스턴스
└── load ThreadPool (worker 4 개) ──┘ 의 메서드를 동시에 호출

한 시점에:

  • store worker 1: core.put_many(...) 호출 중
  • store worker 2: core.put_many(...) 호출 중
  • lookup worker: core.exists_many(...) 호출 중
  • load worker 1, 2, 3, 4: core.load_many_into(...) 호출 중
  • 체크포인트 thread: core._snapshot_state() 호출 중

한 객체의 메서드들이 7~8 개 스레드에서 동시에 실행됨.

이때 모두가 _index 만지면? → race condition (예: dict.items() 순회 중인데 다른 스레드가 dict[key] = ... → 크래시).

이걸 막는 게 self._lock.

두 종류의 lock 구분

exists_many 같은 함수에는 두 종류 lock 이 등장해서 헷갈림:

종류코드의미보호 대상
파이썬 threading lockwith self._lock:같은 프로세스 내 스레드 동기화_index, _lock_refcnt 자체
L2 lock (refcount)_lock_refcnt[key] += 1"이 슬롯 evict 하지 마" 라는 논리적 잠금캐시 슬롯의 데이터

L2 lock = 숫자 카운터일 뿐. 디스크나 OS lock 이 아님.

정리

헷갈림사실
self._lock_index 자체를 잠근다락은 메모리 변수를 잠그는 게 아님 — with 블록 진입을 직렬화
self._lock 이 스레드를 잠근다스레드가 잠기는 게 아니라 — 블록 입구에서 대기
Core 가 자기 스레드에서 돈다Core 는 수동적 객체 — 외부 7~8 개 스레드가 같은 인스턴스 메서드를 동시에 호출
_index 만지는 모든 메서드가 같은 self._lock 통과결과적으로 _index 일관 상태 유지 (= "보호")

한 줄

self._lock 은 "한 번에 한 스레드만 들어와" 출입증. 개발자들이 "_index 만질 때 이 출입증 받자" 컨벤션을 지키므로 보호됨. Core 자체는 스레드 없음 — 외부 ThreadPool 들이 같은 객체를 동시 호출하는 구조라 락이 필요.