CAS (Compare-And-Swap) 명령어와 락-프리(Lock-free) 기초
핵심 인사이트 (3줄 요약)
- 본질: CAS (Compare-And-Swap)는 메모리에 있는 특정 값이 "내가 예상했던 예전 값(Compare)과 똑같을 때만, 새로운 값으로 갈아 끼운다(Swap)"는 두 가지 복합 동작을 절대 쪼개지지 않는 단일 하드웨어 클럭(원자성, Atomic)으로 보장해 주는 CPU 어셈블리 명령어다.
- 가치: 락(Lock, Mutex 등)을 걸고 스레드를 잠재우는 무거운 OS 개입 방식의 한계를 뚫고, 스레드를 재우지 않으면서도 멀티코어 간의 동시성 데이터 꼬임을 완벽히 방어하는 락-프리 (Lock-free) 및 논블로킹 알고리즘의 최하단 척추가 된다.
- 융합: 고성능 자바(Java)의
ConcurrentHashMap부터 운영체제의 스핀락(Spinlock) 구현, 그리고 데이터베이스의 낙관적 동시성 제어(Optimistic Concurrency Control)에 이르기까지, 현대 컴퓨터 공학에서 병렬 스루풋을 극대화하는 모든 마법의 기저에는 CAS가 융합되어 있다.
Ⅰ. 개요 및 필요성 (Context & Necessity)
-
개념:
- 멀티스레드 환경에서 데이터 변경의 원자성(Atomicity, 쪼개질 수 없는 절대적 실행)을 하드웨어 칩셋(CPU) 레벨에서 보장해 주는 명령어다. x86 아키텍처에서는
CMPXCHG명령어 형태로 존재한다. - 작동 방식:
CAS(변수 주소, 기대하는_기존_값, 바꿀_새로운_값)$\rightarrow$ 성공(True) 또는 실패(False) 반환.
- 멀티스레드 환경에서 데이터 변경의 원자성(Atomicity, 쪼개질 수 없는 절대적 실행)을 하드웨어 칩셋(CPU) 레벨에서 보장해 주는 명령어다. x86 아키텍처에서는
-
필요성(문제의식):
- 단순히 코드로
count++를 쳤다고 치자. 겉보기엔 1줄이지만 CPU 내부에서는 3단계로 나뉜다: ① 메모리에서 레지스터로count읽기 $\rightarrow$ ② 레지스터에서+1하기 $\rightarrow$ ③ 결과를 메모리에 쓰기. - 스레드 A가 1단계(읽기)를 하고 2단계를 하려는 찰나, 스레드 B가 끼어들어 1~3단계를 다 해버리면? A가 가진 기존 값은 '옛날 쓰레기 값(Stale data)'이 되어버리고, A가 3단계를 완료하는 순간 B가 고생해서 올린 숫자가 허공으로 증발해 버린다(Race Condition).
- 해결책: "OS의 무거운 락(Mutex)을 걸면 너무 느리다. CPU 칩 설계자에게 부탁해서, '기존 값 비교'와 '새 값 쓰기'를 하는 동안 절대 다른 코어가 끼어들지 못하게 전기적으로 막아버리는(Bus Lock) 단일 명령어를 만들어달라!"
- 단순히 코드로
-
💡 비유:
- 기존 방식 (Race Condition): 도서관 자리에 내 가방을 놓으려고 빈자리인지 확인했다(읽기). 가방을 가지러 1초 뒤돌아본 사이, 남이 그 자리에 앉아버렸는데 나는 그것도 모르고 내 가방을 그 사람 무릎 위에 올려버린다(덮어쓰기 파탄).
- CAS 방식: 내 눈이 자리를 확인하는 순간 내 손이 이미 가방을 던져 놓고 있다. 확인과 차지가 동시에 0.0001초 만에 이루어지므로 누구도 그 사이에 새치기할 틈이 없는 궁극의 밑장빼기 방어 기술이다.
-
등장 배경:
- 1970년대 IBM 메인프레임에서 다중 프로세서 동기화를 위해 도입된
Test-and-Set의 확장판으로, 무거운 커널 문맥 교환 오버헤드(Context Switch)를 회피하려는 현대 고성능 분산 시스템과 스레드 프로그래밍의 절대적 표준이 되었다.
- 1970년대 IBM 메인프레임에서 다중 프로세서 동기화를 위해 도입된
┌─────────────────────────────────────────────────────────────┐
│ 일반 연산(+) vs CAS 명령어의 동시성 제어 비교 │
├─────────────────────────────────────────────────────────────┤
│ │
│ [ 일반 연산의 비극 (Lost Update) ] │
│ 메모리: V = 10 │
│ Thread A: V 읽음(10) -> (인터럽트!) 잠시 멈춤 │
│ Thread B: V 읽음(10) -> V+1 계산(11) -> V에 11 기록. (메모리 V=11) │
│ Thread A: 깨어남 -> 나 아까 10 읽어뒀지! -> V+1 계산(11) -> V에 11 기록│
│ ▶ 결과: 두 번 더했는데 값은 12가 아니라 11! (B의 연산이 씹힘) │
│ │
│ [ CAS 연산의 방어 (Compare And Swap) ] │
│ 메모리: V = 10 │
│ Thread A: V 읽음(10) -> (인터럽트!) 잠시 멈춤 │
│ Thread B: V 읽음(10) -> V+1 계산(11) -> CAS(V, 10, 11) 성공! 메모리 V=11│
│ Thread A: 깨어남 -> 나 아까 10 읽어뒀지! 새 값은 11이다! │
│ ▶ CPU에게 CAS(V, 10, 11) 시도 지시! │
│ ▶ 하드웨어: "어? 너의 기대값은 10인데, 지금 메모리 V는 11이네?" │
│ ▶ 하드웨어: CAS 실패(False) 반환! 덮어쓰기 거부! 🚫 │
│ Thread A: 아차, 늦었구나! 처음부터 다시 읽자. V(11) -> V+1(12) -> CAS 성공!│
└─────────────────────────────────────────────────────────────┘
[다이어그램 해설] 이 그림은 CAS가 어떻게 OS의 락(Lock) 없이도 완벽한 무결성을 지켜내는지 증명한다. CAS의 생명은 '비교(Compare)'에 있다. 스레드 A가 자신이 예전에 읽어두었던 값(10)을 들고 가서 무작정 덮어쓰지 않고, CPU에게 "지금 메모리 값이 아직도 10이면 11로 바꿔줘"라고 조건부 청탁을 넣는다. 만약 그 짧은 틈에 스레드 B가 값을 11로 훔쳐갔다면, 비교가 실패하므로 CPU는 변경을 거부(False)한다. A는 실패를 인지하고 최신 값(11)을 다시 퍼와서(while 루프) 연산을 깔끔하게 재시도(Retry)한다. 이것이 그 유명한 '낙관적 락(Optimistic Lock)'의 실체다.
- 📢 섹션 요약 비유: 서류 결재를 올릴 때 그냥 덮어쓰는 게 아니라, "내가 이 문서를 1.0 버전으로 보고 수정했는데, 혹시 그사이 누가 1.1로 바꿨으면 내 수정본은 버리고 실패 처리해 줘. 내가 최신본 다시 받아서 수정할게"라고 똑똑하게 일하는 최첨단 버전 관리 시스템(Git)과 똑같은 원리입니다.
Ⅱ. 아키텍처 및 핵심 원리 (Deep Dive)
CAS의 내부 의사코드(Pseudo-code)와 하드웨어 잠금
소프트웨어 개발자는 함수 하나 부르는 거지만, 내부적으로는 CPU와 메모리 컨트롤러가 멱살을 잡는 거대한 전기적 통제가 들어간다.
// [CAS의 논리적 동작을 보여주는 의사코드 - 실제로는 한 덩어리의 어셈블리 명령어임]
bool compare_and_swap(int *memory_addr, int expected_value, int new_value) {
// ⬇️ 이 괄호 안의 모든 작업은 전기적으로 다른 코어가 절대 끊을 수 없음 (Atomicity)
// 하드웨어가 시스템 버스(Bus) 자체를 잠가버림 (Bus Lock / Cache Line Lock)
atomic {
if (*memory_addr == expected_value) {
*memory_addr = new_value;
return true; // 성공: 내가 1빠로 고침!
}
return false; // 실패: 누가 그새 값을 바꿨음! 다시 시도해라!
}
}
ABA 문제 (CAS의 치명적 아킬레스건)
CAS 알고리즘이 완벽해 보이지만, "값만 비교한다"는 맹점 때문에 생기는 소름 돋는 논리적 오류가 바로 ABA 문제다.
┌───────────────────────────────────────────────────────────────────┐
│ ABA 문제의 발생 시나리오 (치명적 함정) │
├───────────────────────────────────────────────────────────────────┤
│ │
│ [ 메모리 변수 V의 값: A ] │
│ │
│ 1. Thread 1이 V를 읽음. (기대값: A) │
│ 2. (Thread 1이 컨텍스트 스위치로 기절함) │
│ │
│ 3. Thread 2가 V를 읽고 'B'로 바꿈. (V = B) │
│ 4. Thread 2가 다시 마음이 바뀌어서 V를 옛날 값 'A'로 되돌려놓음. (V = A) │
│ │
│ 5. (Thread 1이 깨어남) │
│ 6. Thread 1이 CAS(V, A, C)를 호출! │
│ -> 하드웨어: "어? 기대값이 A인데, 지금 메모리도 A네? 통과! (Swap성공)" │
│ │
│ 🚨 [ 재앙 발생! ] │
│ Thread 1 입장에서는 값이 A에서 A로 그대로 있었던 줄 알지만, 실제로는 │
│ A -> B -> A 로 한 번 뒤집어졌던 상태다! 만약 저 A가 단순 숫자가 아니라, │
│ 포인터(메모리 주소)인데 중간에 해제(Free)되었다가 재할당(Malloc)된 거라면, │
│ 프로그램 전체가 붕괴(Segfault)되는 끔찍한 사태가 터진다! │
└───────────────────────────────────────────────────────────────────┘
[다이어그램 해설] CAS는 오직 '값'이 같은지만 본다. 이 값이 중간에 다른 일을 겪고 우연히 똑같은 모양으로 돌아왔는지는 알지 못한다. 연결 리스트(Linked List) 큐를 락-프리로 짤 때 노드 주소 A가 해제(Free)된 후 우연히 OS가 똑같은 램 번지(A)를 다른 노드로 재할당해 줬다면, CAS는 옛날 A인 줄 알고 리스트의 머리를 엉뚱한 허공에 꽂아버려 시스템을 파괴한다. 이 무서운 ABA 문제를 해결하기 위해, 현대 아키텍트는 값 옆에 '버전표(Tag/Version)'를 붙여 CAS( [A, ver.1], [C, ver.2] )처럼 더블 워드(Double-word) 64비트 비교를 수행하는 영리한 우회 기법을 쓴다.
- 📢 섹션 요약 비유: 서랍 속에 둔 내 지갑(A)이 안전한지 볼 때 지갑 모양(값)만 보는 게 CAS의 약점입니다. 도둑이 내 지갑(A)을 훔쳐 가서 돈(B)을 빼고, 똑같이 생긴 싸구려 지갑(A)을 다시 채워놓으면(A->B->A), 나는 내 지갑이 무사한 줄 착각하고 거래를 승인해 버리는 치명적 사기 수법에 당하는 꼴입니다.
Ⅲ. 융합 비교 및 다각도 분석
Mutex/Lock(비관적 락) vs CAS(낙관적 락 / Lock-free)
이 둘은 시스템의 처리량(Throughput) 곡선을 완전히 뒤바꿔버리는 두 가지 상반된 종교 철학이다.
| 비교 항목 | 비관적 동기화 (Mutex / Lock) | 낙관적 동기화 (CAS / Lock-free) |
|---|---|---|
| 철학 | "남들이 무조건 훔쳐 갈 거야. 일단 내 자리에 철창(Lock)부터 쳐야지." | "아무도 안 건드렸겠지? 슬쩍 해보고, 엇 누가 건드렸네? 그럼 다시 해야지(Retry)." |
| 스레드 상태 | 락 경합 시 블로킹(Blocking)되어 수면 및 문맥 교환 오버헤드 폭발. | 차단 없이 즉시 실패 반환. 루프(Spin)를 돌며 연산을 무한 재시도. |
| 적합한 환경 | 충돌이 매우 잦고, 한 번 들어가면 오래 작업하는 무거운 로직. | 충돌이 드물고, 연산이 극도로 짧은(단순 덧셈/큐 푸시) 마이크로 로직. |
| 최악의 단점 | 스레드가 꼬이면 영원히 멈추는 **데드락(Deadlock)**의 공포. | 충돌이 극심할 경우, 백만 번 재시도만 하느라 CPU 100% 태우는 Livelock 낭비. |
과목 융합 관점
-
데이터베이스 (Optimistic Concurrency Control): CAS의 마법은 OS를 넘어 거대한 데이터베이스 엔진에도 적용된다. 수만 명의 유저가 동시에 웹페이지에서 상품 재고를 살 때, MySQL 행(Row)에 Lock을 걸어버리면 DB가 마비된다. 대신 테이블에
버전(version)컬럼을 하나 파두고, 업데이트할 때UPDATE stock SET cnt=cnt-1, ver=ver+1 WHERE id=1 AND ver=현재버전;쿼리를 날린다. 만약 적용된 행이 0개라면 누군가 방금 사갔다는 뜻(CAS 실패)이므로, 앱 단에서 예쁘게 "잔여 수량이 변동되었습니다"라고 에러를 띄운다. 이것이 웹 스케일 트래픽을 감당하는 낙관적 락의 정석이다. -
프로그래밍 (Java
java.util.concurrent.atomic): 자바 개발자들이 애용하는AtomicInteger클래스의incrementAndGet()메서드는 겉보기엔 그냥 숫자 +1 이지만, 내부 소스를 까보면 JNI 층을 뚫고 내려가Unsafe.compareAndSwapInt()라는 이 극단적인 하드웨어 어셈블리 명령어를 직접 때리고 있다. C언어의 전유물이던 하드웨어 최적화가 JVM 언어 레벨까지 완전히 융합된 증거다. -
📢 섹션 요약 비유: 비관적 락은 신호등을 세우고 차들이 무조건 빨간불에 서게(Blocking) 만들어 꽉 막히게 하는 교차로라면, CAS는 신호등 없이 차들이 회전교차로에 진입하다가 눈치껏 빈틈이 생기면 쓱 통과하고, 실패하면 빙빙 돌며(Retry) 다시 기회를 엿보는 자율적 흐름의 극치입니다.
Ⅳ. 실무 적용 및 기술사적 판단
실무 시나리오 및 최적화 함정
-
시나리오 — 고빈도 금융 트레이딩 서버의 락(Lock) 지연 사태: 코어 32개짜리 리눅스 서버에서, 주문 체결 카운터를 올리는 데
pthread_mutex_lock을 썼더니, 스레드들이 락을 얻으려고 줄 서다 문맥 교환 오버헤드에 치여 1밀리초 지연이 발생해 거래에 실패했다.- 아키텍트 판단 (원자적 변수로 치환): 카운터 증가, 깃발 세팅 같은 단일 변수 조작에 무거운 뮤텍스 커널 객체를 쓰는 것은 소 잡는 칼로 파리를 써는 격이다. 아키텍트는 즉시 락을 치우고, C++의
std::atomic<int>나 C의__sync_val_compare_and_swap매크로(CAS 명령어 래핑)로 로직을 교체한다. 스레드가 기절(Block)하는 시간 자체가 아예 0으로 소멸하여 TPS가 수십 배로 수직 상승한다.
- 아키텍트 판단 (원자적 변수로 치환): 카운터 증가, 깃발 세팅 같은 단일 변수 조작에 무거운 뮤텍스 커널 객체를 쓰는 것은 소 잡는 칼로 파리를 써는 격이다. 아키텍트는 즉시 락을 치우고, C++의
-
시나리오 — 락-프리 큐(Lock-free Queue) 설계 시의 멀티코어 캐시 파탄: 주니어 개발자가 "락이 없으면 무조건 빠르다"는 맹신으로, 배열 기반의 락프리 큐를 CAS로 구현했다. 32코어에서 돌리자 오히려 뮤텍스보다 성능이 더 떨어지는 기현상이 벌어졌다.
- 원인 분석: 32개의 코어가 1개의 변수(
tail포인터)에 동시에 CAS 명령어를 무한 루프로 때려 박으면, 하드웨어 레벨에서 시스템 메모리 버스와 L1/L2 캐시의 무효화 통신(MESI 프로토콜의 Ping-pong)이 폭증하며 버스 전체가 마비된다. 이것이 바로 CAS의 그림자인 **캐시 스래싱(Cache Thrashing)**이다. - 아키텍트 판단 (Backoff 튜닝): "누가 이미 선점했다면, 바로 재시도하지 말고 살짝 한 템포 쉬어라!" 네트워크의 충돌 회피 알고리즘(CSMA/CD)처럼, CAS가 실패했을 때
cpu_relax()명령어(Intel의PAUSE)나 작은 랜덤 딜레이(Exponential Backoff)를 주어 맹렬한 버스 타격을 부드럽게 분산시키는 아키텍처적 튜닝이 필수적이다. 무식한 CAS 무한 루프는 시스템을 끓어오르게 만드는 독극물이다.
- 원인 분석: 32개의 코어가 1개의 변수(
┌───────────────────────────────────────────────────────────────────┐
│ 시큐어 코딩: 안전한 락-프리(CAS) 루프 작성 템플릿 │
├───────────────────────────────────────────────────────────────────┤
│ │
│ [ 잘못된 예시 (위험도 최상) ] │
│ while (!CAS(&val, old, new)) { │
│ // 빈 루프 💥 (CPU 100% 고갈, 하드웨어 버스 폭발 유발) │
│ } │
│ │
│ [ 아키텍트가 검증한 올바른 예시 (Backoff & Retry 패턴) ] │
│ int expected = *target; │
│ while (!CAS(target, expected, expected + 1)) { │
│ │
│ // 1. 실패했으므로, 그새 남이 바꾼 최신 값을 다시 읽어옴 갱신 │
│ expected = *target; │
│ │
│ // 2. CPU 파이프라인 과열을 막기 위한 하드웨어 힌트 (필수!) │
│ // x86의 경우 PAUSE 명령어로, 불필요한 메모리 접근을 수십 사이클 늦춤 │
│ _mm_pause(); // (또는 cpu_relax(), Thread.yield() 등) │
│ } │
└───────────────────────────────────────────────────────────────────┘
[다이어그램 해설] CAS 기반 프로그래밍은 커널과 프레임워크 개발자들의 전유물처럼 여겨지는 고난도 영역이다. 위 템플릿은 락프리 알고리즘이 절대 놓쳐서는 안 될 두 가지 뼈대(최신 상태 재갱신과, 충돌 회피용 백오프)를 보여준다. 특히 PAUSE 명령어는 하드웨어 칩 설계자(인텔)가 "스레드들이 루프를 도느라 열받아 죽겠으니 제발 이 명령어를 넣어 버스를 좀 쉬게 해달라"고 개발자들에게 간곡히 부탁하여 만든 특별한 기계어다. 소프트웨어 알고리즘이 하드웨어의 물리적 발열과 버스 한계까지 굽어살펴야 하는 극한의 최적화 세계다.
안티패턴
-
복잡한 멀티 변수 동기화에 CAS 강제 적용: 구조체의 이름, 나이, 잔액 등 3~4개의 변수를 한 번에 묶어서 원자적으로 바꿔야 하는데, 이를 락 없이 짜보겠다고 수천 줄짜리 난해한 CAS 상태 머신 로직을 짜는 행위. CAS는 근본적으로 64비트(또는 128비트) "단일 포인터/변수" 1개에만 적용되는 마법이다. 엮인 데이터가 많아질수록 락프리 코드는 사람이 이해 불가능한 버그의 온상이 된다. 이럴 때는 영웅 심리를 버리고 얌전히 전통적 뮤텍스(Mutex) 락 하나를 깔끔하게 거는 것이 가장 우수한 공학적 판단이다.
-
📢 섹션 요약 비유: CAS는 스나이퍼의 총알과 같아서 목표물 하나(변수 1개)를 조용하고 깔끔하게 날려버릴 때 최고의 무기지만, 적군 100명이 몰려오는 광장(복합 자료구조 변경)에서 스나이퍼 총으로 한 명씩 맞추겠다고 고집부리는 건 바보짓입니다. 이럴 땐 시원하게 수류탄(Mutex Lock)을 까서 한방에 통제하는 게 맞습니다.
Ⅴ. 기대효과 및 결론
정량/정성 기대효과
| 구분 | 무거운 Mutex(락) 적용 시 | CAS 기반 락프리(Lock-free) 적용 시 | 개선 효과 |
|---|---|---|---|
| 정량 (문맥 교환 오버헤드) | 스레드 블로킹 시 매번 1~5µs 손실 | 대기 및 문맥 교환 시간 0초 (Zero) | 수천만 건의 단순 데이터 누적 연산 시 시스템 스루풋 극대화 |
| 정량 (응답 레이턴시) | 다른 스레드 지연 시 덩달아 무한정 대기 | 논블로킹 특성으로 즉각적 연산 사이클 보장 | 초저지연(Ultra-low Latency) 시스템의 실시간성 방어 |
| 정성 (시스템 생존율) | 락 잡은 스레드가 죽으면 시스템 영원히 데드락 | 누군가 죽어도 나머지 스레드는 유유히 진행 | 중단점 없는(Non-blocking) 극한의 회복 탄력성(Resilience) 획득 |
미래 전망
- 락-프리(Lock-free)에서 웨이트-프리(Wait-free)로의 도약: CAS를 쓰더라도 운이 나쁜 스레드는 남들이 이기는 걸 보며 수만 번 루프를 도는 기아(Starvation) 상태에 빠질 수 있다. 이를 극복하여 "모든 스레드가 정해진 짧은 시간 안에 반드시 연산을 마침"을 수학적으로 보장하는 궁극의 경지, 웨이트-프리(Wait-free) 알고리즘 연구가 분산 스트리밍 엔진과 RTOS의 핵심 난제로 떠오르고 있다.
- 하드웨어 트랜잭셔널 메모리 (HTM): CAS 명령어 하나로는 1개의 변수밖에 못 바꾼다는 한계를 타파하기 위해, 인텔은 아예 CPU 캐시 레벨에서
xbegin~xend로 감싼 코드 블록 전체(여러 변수 수정 포함)를 하나의 큰 원자적 트랜잭션으로 묶어버리는 TSX(Transactional Synchronization Extensions) 기술을 내놓았다. 바야흐로 소프트웨어가 락을 쥐어짜던 시대에서, 실리콘 칩이 락을 자체 소멸시키는 방향으로 하드웨어 진화가 폭발하고 있다.
참고 표준
- x86 Instruction Set (CMPXCHG): 인텔 및 AMD 아키텍처에서 비교 후 교환의 원자성을 보장하기 위해 버스 락(LOCK prefix)과 함께 쓰이는 핵심 기계어 표준.
- C11/C++11 Memory Order: CAS 명령어를 감싸는
std::atomic라이브러리에서, 컴파일러나 CPU가 코드를 맘대로 재배치(Reordering)하는 것을 막기 위해memory_order_acquire/release같은 미세한 메모리 가시성 장벽을 언어 표준으로 확립.
CAS (Compare-And-Swap) 명령어는 컴퓨터 과학자들이 '물리적 마찰(Lock)' 없이 다중 우주(Multithreading)의 질서를 통제하기 위해 빚어낸 가장 아름답고 날카로운 단 하나의 검(Sword)이다. 거대한 운영체제가 멈추지 않고 부드럽게 흐르는 환상, 수십만 명의 접속자가 동시에 장바구니에 물건을 담아도 꼬이지 않는 마법. 이 거대한 현대 IT 인프라의 마천루를 떠받치는 가장 깊고 좁은 주춧돌을 파고들면, 결국 1 나노초의 찰나를 번개처럼 가르는 이 작은 하드웨어 기계어 하나가 버티고 서 있다.
- 📢 섹션 요약 비유: 수백 대의 열차가 엉키는 교차로에서 경찰관(OS Mutex)이 일일이 수신호로 차를 세우고 보내는 무거운 방식을 치워버리고, 모든 자동차 자체에 자율주행 충돌 방지 센서(CAS 하드웨어)를 달아 알아서 부드럽게 피해 가고 틈새를 파고들게 만드는 완벽한 미래 교통 제어 시스템입니다.
📌 관련 개념 맵 (Knowledge Graph)
| 개념 명칭 | 관계 및 시너지 설명 |
|---|---|
| 스핀락 (Spinlock) | 커널이 멀티코어 환경에서 초고속으로 공유 자원을 잠글 때, 내부적으로 바로 이 CAS 명령어를 무한 루프로 때려서 구현하는 가장 대표적인 고객이다. |
| Race Condition (경쟁 조건) | 두 스레드가 값을 덮어쓰려다 데이터가 깨지는 참사로, CAS는 이것을 원천 차단하기 위해 탄생한 방패다. |
| ABA 문제 | 값이 A에서 B로, 다시 A로 돌아왔을 때 중간의 변화를 눈치채지 못하고 허락해 버리는 CAS의 가장 유명하고 치명적인 알고리즘적 한계다. |
| 낙관적 동기화 (Optimistic Lock) | 락을 일단 안 걸고 대충 계산한 뒤, 마지막 반영 순간에 "어? 누가 안 건드렸네? 성공!"을 외치는 트랜잭션 기법으로 CAS가 이 철학의 실체다. |
| 메모리 배리어 (Memory Barrier) | CAS 자체는 값을 안전하게 바꾸지만, 컴파일러나 CPU가 명령어 순서를 뒤죽박죽으로 최적화하는 것을 막기 위해 동반되어야 하는 투명한 장벽이다. |
👶 어린이를 위한 3줄 비유 설명
- 서랍 속에 초콜릿 10개가 있는지 확인하고, 그 자리에 11개로 채워놓으려고 해요. 보통은 서랍을 열고 확인한 뒤에 다시 손을 뻗어 채워 넣죠.
- 그런데 손을 뻗는 0.1초 사이에 얄미운 동생이 초콜릿을 하나 훔쳐가면, 나는 9개가 된 줄도 모르고 바보처럼 11개를 넣어버리게 돼요(데이터 꼬임).
- CAS 마법은 "내 눈이 10개인 걸 확인하는 그 완벽한 찰나의 순간에 빛의 속도로 11개짜리 주머니와 싹 바꿔치기" 하는 기술이에요. 동생이 아예 손을 내밀 틈을 안 주는 엄청난 방어 기술이랍니다!