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 lock | with 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 들이 같은 객체를 동시 호출하는 구조라 락이 필요.