이미지 처리, 추론 파이프라인, 고성능 시스템을 다루다 보면
자연스럽게 “스레드”와 “allocator”라는 단어를 만나게 된다.
이 둘은 각각 따로 보면 단순하지만, 같이 쓰이기 시작하는 순간 매우 중요한 설계 포인트가 된다.
이 글에서는
- 스레드란 무엇인지
- allocator란 무엇인지
- 왜 이 둘의 관계를 반드시 이해해야 하는지
를 기초부터 정리한다.
스레드(Thread)란 무엇인가
정의
스레드는 프로그램 안에서 실제로 코드를 실행하는 실행 흐름 단위다.
- 하나의 프로세스 안에 여러 스레드가 존재할 수 있다
- 각 스레드는 독립적인 실행 순서를 가진다
- 여러 스레드는 동시에 실행될 수 있다
중요한 점은
서로 다른 스레드가 같은 코드, 같은 데이터에 동시에 접근할 수 있다
는 사실이다.
스레드 자체가 문제가 아니라, “공유”가 문제다
스레드가 위험한 이유는 스레드 그 자체가 아니라
여러 스레드가 하나의 자원을 공유할 때 발생한다.
예를 들면:
- 같은 이미지 버퍼
- 같은 결과 구조체
- 같은 메모리 관리자
이런 공유 자원에 대해
두 스레드가 동시에 읽고 쓰기를 하면 경쟁 상태(race condition) 가 발생한다.
이 버그는
- 재현이 어렵고
- 환경에 따라 나타났다 사라지고
- 디버깅이 매우 까다롭다
그래서 멀티스레드 환경에서는
“무엇을 공유하는가”가 가장 중요해진다.
Allocator란 무엇인가
정의
allocator는 메모리를 어떻게 할당하고, 어떻게 회수할지를 관리하는 객체다.
주요 역할은 다음과 같다.
- 메모리 할당
- 메모리 해제
- 재사용(pooling)을 통한 성능 최적화
일반적인 new, malloc 대신
전용 allocator를 쓰는 이유는 성능 때문이다.
특히
- 같은 크기의 메모리를 자주 쓰거나
- 짧은 생명주기의 임시 버퍼가 많을 때
allocator는 큰 차이를 만든다.
allocator는 “상태를 가진 객체”다
여기서 중요한 포인트가 나온다.
allocator는 내부적으로 다음과 같은 상태(state) 를 가진다.
- 다음에 줄 메모리 위치
- 어떤 블록이 사용 중인지 여부
- 사용 가능한 풀의 구조
즉,
allocator는 단순한 함수가 아니라
내부 상태를 계속 수정하는 객체다.
스레드와 allocator가 만나면 생기는 문제
여러 스레드가 하나의 allocator에 동시에 접근하면 어떤 일이 생길까?
예를 들어:
- 스레드 A가 메모리를 할당하려는 순간
- 스레드 B도 동시에 할당을 시도한다면
allocator 내부 상태가 동시에 수정되면서
- 같은 메모리를 두 번 주거나
- 내부 구조가 깨질 수 있다
이 문제를 막기 위해 선택지는 두 가지다.
PoolAllocator와 UnlockedPoolAllocator의 차이
PoolAllocator
- 내부 상태를 락(mutex)으로 보호
- 여러 스레드가 동시에 접근해도 안전
- 대신 락으로 인한 성능 비용이 존재
UnlockedPoolAllocator
- 내부 상태 보호 없음
- 단일 스레드에서만 사용 가능
- 락이 없어서 더 빠름
즉, 차이는 단순하다.
- 안전함 vs 성능
- 멀티스레드 가능 vs 단일 스레드 전용
“단일 스레드가 보장된다”는 말의 진짜 의미
이 말은 단순히
- 지금은 하나의 스레드에서만 쓰인다
라는 뜻이 아니다.
의미는 다음과 같다.
allocate와 free가
항상 동일한 하나의 스레드에서만 호출됨이 설계와 구조 차원에서 확정된 상태
아래 중 하나라도 가능하다면
단일 스레드 보장이 아니다.
- 다른 스레드에서 free가 호출될 수 있음
- 비동기 콜백이 다른 스레드에서 실행됨
- 스레드풀에서 작업이 실행됨
이 경우 UnlockedPoolAllocator를 공유해서 쓰는 것은 위험하다.
정리
- 스레드는 코드를 실행하는 단위다
- allocator는 메모리를 관리하는 상태 가진 객체다
- 여러 스레드가 하나의 allocator를 공유하면 문제가 생길 수 있다
- 해결 방법은 두 가지다
- 락이 있는 allocator 사용
- 스레드마다 allocator 분리
이 개념을 이해하면
“왜 이 상황에서 이 allocator를 써야 하는가”를
감이 아니라 설계 기준으로 판단할 수 있게 된다.