Lock-Free Programming까지 공부하고 나면 처음에는 “이 정도면 락 없이도 꽤 안전하게 동시성을 처리할 수 있겠는데?”라는 생각이 들기 쉽다. 나도 처음에는 CAS(Compare-And-Swap) 같은 원자 연산이 거의 완벽한 해결책처럼 느껴졌다. 락 없이도 여러 스레드가 동시에 데이터를 수정할 수 있고, CPU도 기다리는 시간을 줄일 수 있으니 굉장히 효율적으로 보였기 때문이다. 그런데 조금 더 깊게 들어가 보니까 Lock-Free 구조에도 꽤 무서운 함정이 숨어 있었고, 그 대표적인 문제가 바로 ABA Problem이었다.
처음 이름을 들었을 때는 솔직히 왜 이런 이름이 붙었는지 잘 이해가 안 됐다. 그런데 구조를 이해하는 순간 바로 감이 왔다. “값은 원래대로 돌아왔지만, 중간에 뭔가 중요한 변화가 있었다”는 게 핵심이었기 때문이다.

CAS는 ‘값이 그대로인지’만 확인한다
Lock-Free 구조에서는 CAS(Compare-And-Swap)가 굉장히 자주 사용된다. 쉽게 말하면 “현재 값이 내가 예상한 값과 같으면 새로운 값으로 바꿔라”라는 방식이다.
예를 들어 어떤 스레드가 변수 값을 읽었고, 그 값이 아직 안 바뀌었다면 안전하게 수정할 수 있다고 판단하는 구조다.
처음에는 나도 이 방식이 굉장히 똑똑해 보였다. 락 없이도 충돌 여부를 확인할 수 있고, CPU가 계속 기다리지 않아도 되기 때문이다.
특히 멀티코어 환경에서는 락 경쟁(lock contention)이 성능 문제를 만들 수 있기 때문에, CAS 기반 구조는 현대 서버나 고성능 시스템에서 굉장히 중요하게 사용된다고 한다.
그런데 문제는 CAS가 “현재 값”만 본다는 점이었다. 즉, 중간 과정에서 어떤 일이 있었는지는 알 수 없다.
나도 처음에는 “값만 같으면 괜찮은 거 아닌가?”라고 생각했는데, 실제 동시성 환경에서는 중간 변화 자체가 굉장히 중요한 경우가 많다는 걸 나중에 알게 됐다.
ABA Problem은 값이 원래대로 돌아와도 문제가 생길 수 있다는 뜻이다
ABA Problem의 핵심은 이름 그대로다.
- 처음 값이 A였다.
- 중간에 다른 스레드가 값을 B로 바꿨다.
- 다시 A로 돌아왔다.
겉으로 보면 결국 값은 A다. 그래서 CAS 입장에서는 “아무 변화 없음”처럼 보인다.
하지만 실제로는 중간에 중요한 상태 변화가 이미 발생했을 수도 있다.
예를 들어 스택(Stack) 같은 자료구조에서 어떤 노드를 제거했다가 다시 재사용하는 상황을 생각해보면 문제가 더 잘 드러난다. 어떤 스레드는 “아직 원래 노드가 그대로 있다”고 생각하고 작업을 진행하지만, 실제로는 중간에 다른 스레드가 데이터를 변경하고 메모리 상태까지 바꿔버렸을 수도 있는 것이다.
나도 처음 이 개념을 봤을 때 꽤 충격이었다. CAS가 단순하고 안전한 구조처럼 느껴졌는데, 실제로는 “중간에 어떤 변화가 있었는지”까지는 알 수 없다는 한계가 있었기 때문이다.
특히 동시성 문제는 겉으로 결과가 멀쩡해 보여도 내부 상태는 이미 꼬여 있는 경우가 많다는 점이 무서웠다.
그 이후로는 “값이 같다”와 “상태가 같다”는 게 완전히 다른 의미라는 걸 조금 이해하게 됐다.
그래서 Lock-Free 구조는 생각보다 훨씬 복잡하다
ABA Problem이 어려운 이유는 단순히 버그 하나 때문이 아니다. 결국 Lock-Free 구조 전체가 “동시에 움직이는 아주 짧은 순간들” 위에서 동작하고 있기 때문이다.
특히 현대 CPU는:
- Out-of-Order Execution
- Speculative Execution
- Cache 구조
- Memory Reordering
같은 최적화들을 계속 수행하고 있다.
즉, 개발자는 단순히 코드 흐름만 생각하는 게 아니라 CPU 내부에서 실제로 어떤 순서로 데이터가 보일지까지 고려해야 하는 상황이 되는 것이다.
나도 처음에는 “락 없이 처리하면 그냥 더 빠른 구조인가 보다” 정도로 생각했는데, Lock-Free 프로그래밍 관련 자료들을 보다 보니까 거의 CPU 구조와 메모리 시스템을 함께 이해해야 하는 수준이라는 걸 느끼게 됐다.
그래서 ABA Problem을 해결하기 위해서는 단순 값 비교 대신 버전(version) 번호를 추가하거나, 포인터 상태까지 함께 관리하는 방식들이 사용되기도 한다.
결국 현대 서버 개발은 단순히 빠르게 만드는 문제가 아니라, 수많은 코어와 캐시, 메모리 흐름 속에서도 “상태가 정말 안전한가”를 계속 증명해야 하는 세계라는 생각이 들었다.
이걸 공부하면서 가장 인상 깊었던 건 컴퓨터가 단순 계산기가 아니라는 점이었다. 내부에서는 아주 짧은 순간에도 수많은 상태 변화와 경쟁이 발생하고 있었고, Lock-Free 구조는 그 위에서 아슬아슬하게 균형을 유지하고 있었던 것이다.
한 줄로 정리하면 ABA Problem은 값이 A에서 B를 거쳐 다시 A로 돌아왔을 때 CAS가 이를 변화 없는 상태로 착각하는 문제이며, Lock-Free 구조에서 숨겨진 동시성 버그를 만드는 대표적인 함정이다.