핵심 인사이트 (3줄 요약)

  1. 본질: CAS (Compare-and-Swap) 연산은 공유 메모리의 값이 내가 기대한 값과 같을 때만 새 값으로 바꾸는 원자적(Atomic) 읽기-수정-쓰기(Read-Modify-Write) 명령이다.
  2. 가치: 락(Lock)으로 스레드를 재우지 않고도 충돌을 검출할 수 있어, 짧은 임계 갱신에서는 높은 처리량과 낮은 지연시간을 동시에 노릴 수 있다.
  3. 판단 포인트: CAS는 만능이 아니라 충돌이 잦아지면 재시도 비용과 캐시 라인 경쟁이 커지므로, ABA 문제·메모리 순서·경합 분산까지 함께 설계해야 한다.

Ⅰ. 개요 및 필요성

CAS (Compare-and-Swap) 연산은 멀티코어 환경에서 공유 변수를 안전하게 갱신하기 위해 CPU (Central Processing Unit)가 제공하는 대표적인 원자 명령이다. 핵심은 단순하다. 어떤 스레드가 값을 읽어 계산을 마친 뒤, "내가 처음 봤던 값이 아직 그대로인가" 를 확인하고 그때만 결과를 반영한다.

이 명령이 필요한 이유는 일반적인 load -> add -> store가 세 단계로 쪼개지기 때문이다. 예를 들어 카운터가 10일 때 두 스레드가 동시에 1씩 증가시키면, 둘 다 10을 읽고 각각 11을 써서 최종 결과가 12가 아니라 11이 되는 갱신 손실(Lost Update)이 발생할 수 있다. 락으로 막을 수도 있지만, 락은 경쟁 시 스레드를 대기열에 세우고 OS (Operating System)의 스케줄링 개입까지 유발해 비용이 커진다.

CAS는 이 문제를 낙관적 동기화(Optimistic Synchronization) 로 바꾼다. 먼저 계산은 각 코어가 독립적으로 수행하고, 마지막 반영 순간에만 충돌 여부를 판정한다. 충돌이 없으면 한 번에 성공하고, 충돌이 있으면 실패를 반환해 다시 시도하면 된다. 즉, "항상 막아두는 방식"이 아니라 "마지막 순간만 검표하는 방식"이다.

┌──────────────────────────────────────────────────────────────┐
│     왜 CAS가 필요한가: 읽기와 쓰기 사이의 틈을 막아야 함      │
├──────────────────────────────────────────────────────────────┤
│ 스레드 A: 값 읽기(10) ── 계산(11) ── 쓰기(11)                │
│ 스레드 B: 값 읽기(10) ── 계산(11) ── 쓰기(11)                │
│                                                              │
│ 일반 연산 결과: 10 -> 11  (한 번 증가가 사라짐)              │
│ CAS 결과    : 한 스레드만 성공, 다른 스레드는 실패 후 재시도  │
└──────────────────────────────────────────────────────────────┘

결국 CAS는 공유 데이터 갱신을 "무조건 순서대로 줄 세우는 문제"에서 "충돌을 빠르게 감지하고 재시도하는 문제"로 바꿔 준다. 그래서 멀티코어 동기화에서 락프리(Lock-free) 알고리즘의 출발점으로 자주 등장한다.

  • 📢 섹션 요약 비유: CAS는 창구 앞 문을 잠가 한 사람씩 들이는 방식이 아니라, 모두가 서류를 미리 작성해 오고 마지막 도장 찍는 순간에만 "지금 번호표가 아직 네 차례냐"를 확인하는 방식과 같다.

Ⅱ. 아키텍처 및 핵심 원리

CAS의 내부 동작은 비교(Compare) -> 조건부 교체(Swap) 두 단계처럼 보이지만, 하드웨어는 이를 하나의 원자 구간으로 실행한다. x86 계열에서는 CMPXCHG 같은 명령으로 제공되고, 다른 아키텍처에서는 LL/SC (Load-Linked / Store-Conditional) 조합으로 비슷한 효과를 낸다. 중요한 점은 소프트웨어가 둘로 나눠 볼 수 있어도, 하드웨어 관점에서는 중간에 다른 코어가 끼어들 수 없도록 보장된다는 것이다.

구성 요소역할설계상 의미
메모리 위치실제 공유 값이 저장된 주소경쟁이 집중되는 지점
기대값 (Expected Value)내가 이전에 읽어 두었던 값충돌 검출 기준
새 값 (New Value)계산 결과로 쓰고 싶은 값성공 시 반영되는 결과
성공/실패 반환갱신 여부 알림재시도 여부 판단

아래 그림은 CAS가 메모리 갱신을 어떻게 단일 판정으로 묶는지 보여준다.

┌──────────────────────────────────────────────────────────────┐
│                CAS의 원자적 판정 흐름                        │
├──────────────────────────────────────────────────────────────┤
│ 1) 스레드가 old 값을 읽음                                    │
│ 2) 코어 내부에서 new 값을 계산                               │
│ 3) CAS(addr, expected=old, new) 실행                         │
│                                                              │
│    현재 메모리 값 == expected ?                              │
│            ├─ 예  ──> new 기록, 성공 반환                    │
│            └─ 아니오 ─> 값 유지, 실패 반환                   │
└──────────────────────────────────────────────────────────────┘

실제 칩에서는 이 순간 해당 캐시 라인에 대한 독점권을 확보해야 한다. 현대 멀티코어는 버스 전체를 잠그기보다 MESI (Modified, Exclusive, Shared, Invalid) 같은 캐시 일관성 프로토콜을 이용해 특정 캐시 라인만 배타적으로 다룬다. 그래서 CAS의 비용은 단순 명령어 1개 비용이 아니라, 캐시 라인을 누구 소유로 만들 것인가 까지 포함한 비용이다.

또 하나의 장점은 실패한 CAS가 보통 값을 바꾸지 않는다는 점이다. Test-and-Set처럼 실패할 때마다 무조건 쓰기를 발생시키는 방식보다, 불필요한 무효화(Invalidate) 트래픽을 줄일 수 있다. 그래서 스핀락(Spin Lock)보다 더 일반적인 락프리 자료구조에 적합하다.

다만 CAS는 원자성만 줄 뿐, 항상 올바른 메모리 순서(Memory Ordering) 까지 자동으로 해결해 주는 것은 아니다. 어떤 데이터는 먼저 쓰고, 그 뒤에 상태 플래그를 공개해야 한다면 메모리 배리어(Memory Barrier)나 acquire/release 의미론까지 같이 고려해야 한다.

  • 📢 섹션 요약 비유: CAS는 서류를 창구에 던져 넣는 일이 아니라, 창구 직원이 내 번호표와 현재 전광판 번호를 동시에 확인한 뒤 맞으면 즉시 처리하고 아니면 그대로 돌려보내는 즉석 판정기와 같다.

Ⅲ. 비교 및 연결

CAS를 제대로 이해하려면 다른 동기화 방식과의 경계를 봐야 한다. 핵심 차이는 "남을 멈추게 하느냐""실패를 감수하고 다시 하느냐" 에 있다.

항목락 기반 동기화Test-and-SetCAS
기본 철학먼저 잠그고 작업잠금 획득 여부를 원자적으로 판정조건부 갱신으로 충돌 감지
충돌 시 행동스레드 대기/수면 가능계속 회전하며 재시도실패 반환 후 다시 계산
쓰기 특성락 변수 변경 중심실패해도 쓰기 발생조건이 맞을 때만 실제 갱신
적합한 대상긴 임계 구역매우 짧은 락 획득짧은 공유 상태 갱신, 락프리 구조
주의점문맥 교환 비용캐시 스래싱ABA 문제, 재시도 폭증

CAS는 락보다 빠를 수 있지만, 항상 그렇지는 않다. 경합이 낮고 임계 구역이 짧을 때는 CAS의 낙관적 접근이 유리하다. 반대로 하나의 전역 카운터에 수십 개 코어가 동시에 몰리면 모두가 같은 캐시 라인을 두드리며 실패-재시도를 반복하므로, 오히려 CPU 시간을 더 태울 수 있다.

여기서 등장하는 대표적 함정이 ABA 문제다. 스레드가 값 A를 읽은 뒤 잠시 멈춘 사이 다른 스레드가 A -> B -> A로 바꾸면, CAS는 다시 A를 보고 "변화가 없었다"고 오판할 수 있다. 정수 카운터에서는 문제가 없을 수도 있지만, 포인터 기반 스택·큐에서는 노드가 한 번 제거됐다가 재사용되는 순간 심각한 구조 손상이 생길 수 있다.

그래서 CAS는 메모리 배리어, 버전 태그(tag), 참조 카운트, 해저드 포인터(Hazard Pointer) 같은 보조 기법과 자주 연결된다. 즉 CAS는 단독 해법이 아니라, 락프리 설계의 중심축 이다. 주변 안전장치가 함께 붙어야 실제 시스템에서 신뢰성을 얻는다.

  • 📢 섹션 요약 비유: 락은 교차로에 신호등을 세워 모두를 멈추게 하는 방식이고, CAS는 회전교차로에서 빈 틈이 보이면 들어가되 부딪히면 한 바퀴 더 도는 방식과 같다. 다만 같은 차가 잠깐 나갔다 다시 들어와도 같은 차로 착각하는 문제가 바로 ABA다.

Ⅳ. 실무 적용 및 기술사 판단

실무에서 CAS는 AtomicInteger, std::atomic, 락프리 스택, 작업 큐, 참조 카운터처럼 갱신 단위가 작고 실패 시 다시 계산해도 부담이 크지 않은 곳 에서 가장 효과적이다. 예를 들어 조회 수 증가, 상태 전이 플래그, 작업 포인터 이동 같은 연산은 CAS 한 번으로 충분히 해결되는 경우가 많다.

반면 비즈니스 로직이 길거나, 한 번 실패하면 다시 계산하는 비용이 큰 연산에는 CAS가 오히려 부적합하다. 예를 들어 긴 연결 리스트 수정, 복잡한 메모리 할당/해제, 외부 입출력(I/O)과 결합된 갱신은 재시도 비용이 커서 락 기반 설계가 더 단순하고 안전할 수 있다. 기술사 답안에서는 "CAS가 빠르다"가 아니라 "짧은 갱신에는 채택, 긴 임계 구역에는 회피" 로 정리해야 한다.

실무 판단 체크리스트

  1. 공유 데이터 갱신이 짧고 순수 메모리 연산으로 끝나는가?
  2. 실패 시 다시 계산해도 비용이 작은가?
  3. 경쟁 지점이 한 캐시 라인에 과도하게 몰리지 않는가?
  4. 포인터 구조라면 ABA 방지책(버전 태그, 해저드 포인터 등)이 있는가?
  5. 공개 순서가 중요한 데이터라면 acquire/release 또는 fence 설계가 포함되어 있는가?

대표 안티패턴

  • 전역 핫스폿 카운터 하나에 모든 스레드를 몰아 CAS로만 버티는 설계
  • 메모리 해제 문제를 무시한 채 락프리 연결 자료구조를 구현하는 설계
  • 원자성만 믿고 가시성·순서 보장을 생략하는 설계

실무에서는 고경합 카운터에 LongAdder류의 분산 카운터를 쓰거나, 큐를 샤딩(Sharding)해서 경쟁 지점을 줄이는 방식이 자주 선택된다. 즉 CAS의 진짜 활용 포인트는 "무조건 락 제거"가 아니라 경쟁을 줄인 뒤 마지막 갱신만 원자화하는 것 이다.

  • 📢 섹션 요약 비유: CAS는 계산이 짧은 계산대에는 잘 맞지만, 손님 한 명당 상담이 오래 걸리는 창구에는 맞지 않는다. 그런 곳에서 모두를 줄 세우지 않겠다고 우기면, 대기 대신 재시도만 끝없이 늘어나 더 혼잡해진다.

Ⅴ. 기대효과 및 결론

CAS를 적절히 쓰면 컨텍스트 스위치(Context Switch) 비용을 줄이고, 짧은 공유 상태 갱신에서 높은 처리량을 얻을 수 있다. 또한 락을 줄이면 데드락(Deadlock) 가능성을 낮추고, 커널 개입 없이 사용자 공간에서 동시성 제어를 끝낼 수 있어 저지연 시스템에 유리하다.

하지만 전제조건도 분명하다. CAS는 충돌이 낮아야 빛나며, 메모리 재활용·ABA·배리어 설계가 복잡해질수록 구현 난도가 급격히 올라간다. 그래서 현대 시스템은 CAS 하나로 모든 문제를 푸는 대신, 필요 시 락·샤딩·배치·하드웨어 트랜잭셔널 메모리(HTM, Hardware Transactional Memory) 같은 기법과 조합한다.

정리하면 CAS는 "락이 없는 마법"이 아니라, 짧은 갱신을 조건부로 원자화하는 정밀 도구 다. 멀티코어 동기화에서 기억해야 할 핵심은 속도 자체보다, 충돌 확률과 자료구조 특성에 맞게 낙관적 재시도가 성립하는지를 판단하는 것이다.

  • 📢 섹션 요약 비유: CAS는 모든 공사를 망치 하나로 해결하는 만능 도구가 아니라, 나사를 빠르게 조일 때는 최고지만 콘크리트를 부수는 일에는 맞지 않는 정밀 전동드라이버와 같다.

📌 관련 개념 맵

개념연결 포인트
하드웨어 동기화 (Hardware Synchronization)CAS는 CPU가 제공하는 대표적인 원자 명령어다.
Test-and-Set락 획득에는 단순하지만, 실패 시에도 쓰기를 유발해 트래픽이 커진다.
메모리 배리어 (Memory Barrier)CAS의 원자성만으로 부족한 공개 순서와 가시성을 보강한다.
ABA 문제값이 되돌아왔을 때 CAS가 변화를 놓칠 수 있는 대표적 함정이다.
락프리 (Lock-free) 자료구조CAS를 기반으로 스택, 큐, 참조 카운터 등을 비차단으로 구성한다.

📈 관련 키워드 및 발전 흐름도

공유 데이터 경합
    │
    ▼
하드웨어 동기화 (Hardware Synchronization)
    │
    ├─ Test-and-Set
    │
    └─ Compare-and-Swap (CAS)
            │
            ├─ 락프리 (Lock-free) 자료구조
            │
            ├─ ABA 문제 대응
            │     └─ 버전 태그 · 해저드 포인터
            │
            └─ 메모리 배리어 · Acquire/Release
                    │
                    ▼
        고성능 동시성 라이브러리 · HTM

이 흐름은 "원자 명령어 등장 -> 조건부 갱신 고도화 -> 안전장치 결합 -> 고성능 동시성 확장"의 발전 방향을 보여준다.

👶 어린이를 위한 3줄 비유 설명

  1. CAS는 서랍 안 물건이 내가 본 그대로일 때만 새 물건으로 바꿀 수 있게 해 주는 약속이에요.
  2. 누가 먼저 바꿨으면 억지로 덮어쓰지 않고 "앗, 다시 확인해야지" 하고 처음부터 다시 해요.
  3. 그래서 친구들이 한 서랍을 같이 써도 덜 부딪히지만, 너무 많은 친구가 한꺼번에 몰리면 다시 확인하느라 오히려 바빠질 수 있어요.