---
title: "원자적 연산: 쪼개지지 않는 갱신과 메모리 순서는 다르다"
author: "Lee"
published_at: "2026-10-06T10:08:00.727Z"
updated_at: "2026-09-30T09:39:36.331Z"
tags: ["컴퓨터공학","운영체제"]
canonical_url: "https://ariser.io/@writer_a1cd615ab19e/%EC%9B%90%EC%9E%90%EC%A0%81-%EC%97%B0%EC%82%B0-%EC%AA%BC%EA%B0%9C%EC%A7%80%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B0%B1%EC%8B%A0%EA%B3%BC-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%88%9C%EC%84%9C%EB%8A%94-%EB%8B%A4%EB%A5%B4%EB%8B%A4"
ai_assisted: false
---

# 원자적 연산: 쪼개지지 않는 갱신과 메모리 순서는 다르다

[원문](https://ariser.io/@writer_a1cd615ab19e/%EC%9B%90%EC%9E%90%EC%A0%81-%EC%97%B0%EC%82%B0-%EC%AA%BC%EA%B0%9C%EC%A7%80%EC%A7%80-%EC%95%8A%EB%8A%94-%EA%B0%B1%EC%8B%A0%EA%B3%BC-%EB%A9%94%EB%AA%A8%EB%A6%AC-%EC%88%9C%EC%84%9C%EB%8A%94-%EB%8B%A4%EB%A5%B4%EB%8B%A4)

원자적 연산은 다른 실행 흐름이 관찰하는 관점에서 중간 상태로 쪼개지지 않는 연산이다. 공유 카운터 증가처럼 읽기와 변경이 하나의 동작이어야 할 때 사용한다. 원자성은 기계 명령 하나로 보이느냐보다 언어와 API가 어떤 관찰 결과를 보장하는지에 관한 약속이다. 구현은 CPU 명령이나 라이브러리 내부 동기화를 사용할 수 있다.

![현재 값이 기대값 10과 같으면 11로 바꾸고 이미 12라면 실패해 값을 유지하는 비교 교환 그림](/api/v1/media/5ee6350f-480d-4b44-834d-87ab2b5cc22e)

*비교 교환은 값이 기대한 상태일 때만 갱신하며 실패 시 재평가할 수 있다.*

## 읽기와 쓰기를 묶는 동작

원자적인 읽기와 쓰기만 따로 사용해도 증가 전체가 원자적이 되는 것은 아니다. 두 스레드가 값을 읽은 뒤 각자 1을 더해 저장하면 갱신이 사라질 수 있다. `fetch_add` 같은 읽기수정쓰기 연산은 현재 값을 읽고 증가시키는 절차를 불가분으로 묶는다. 반환값으로 각 작업이 서로 다른 순번을 받을 수도 있다.

CAS, 즉 비교 후 교환은 값이 예상한 것과 같을 때만 새 값으로 바꾼다. 카운터가 10일 것으로 예상해 11로 바꾸려는데 다른 작업이 이미 12로 바꿨다면 실패한다. 작업은 현재 값을 다시 관찰하고 새 변경안을 계산해 재시도할 수 있다. 이 방식은 다른 작업의 변경을 모르고 덮어쓰는 일을 막는다.

## 실패도 정상적인 제어 흐름이다

약한 CAS는 값이 맞아도 허용된 이유로 실패할 수 있어 일반적으로 반복문 안에서 사용한다. 강한 CAS와 실패 시 예상값 갱신 등의 세부 규칙은 API를 따라야 한다. [GCC의 원자적 연산 문서](https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html)는 비교 교환과 메모리 순서, 항상 무잠금으로 구현되지 않는 경우를 설명한다. 원자적이라는 말과 락 프리라는 말은 동일하지 않다.

CAS에는 ABA 문제도 있다. 관찰한 값이 A에서 B로 바뀌었다가 다시 A가 됐다면 숫자 비교만으로 중간 변경을 알아채지 못한다. 포인터가 가리키는 객체의 수명까지 관련되면 특히 위험하다. 버전 정보나 안전한 메모리 회수 같은 추가 설계가 필요할 수 있으며, CAS를 사용했다는 이유만으로 자료구조 전체가 안전해지지는 않는다.

## 다른 자료가 보이는 순서

원자성은 한 위치의 갱신을 보호하지만, 다른 위치의 데이터가 언제 보이는지는 메모리 순서 규칙이 다룬다. 생산자가 데이터를 채운 뒤 준비 플래그를 세우고 소비자가 플래그를 확인한 뒤 데이터를 읽는다면, 적절한 release/acquire 같은 동기화 관계가 필요하다. 단순히 플래그가 원자적이라는 사실만으로 모든 다른 접근의 순서를 보장하지 않는다.

약한 순서 규칙은 불필요한 제약을 줄일 수 있지만 요구하는 보장을 정확히 설명해야 한다. 반대로 강한 규칙을 사용해도 서로 다른 변수 사이의 복합 불변식이 저절로 하나의 트랜잭션이 되지 않는다. 잔액 확인과 출금, 두 포인터의 동시 갱신처럼 여러 단계가 한 약속을 구성한다면 더 큰 동기화 설계가 필요하다.

공유 카운터, 작업 인덱스 배분, 안전하게 설계된 큐 등에 원자적 연산이 쓰인다. 경쟁이 심하면 같은 캐시 라인의 소유권을 여러 코어가 주고받고, 재시도가 늘어 느려질 수 있다. 잠금이 없다는 사실을 성능 보증으로 받아들이지 말고 정확성, 진행 보장, 메모리 수명, 경합 비용을 각각 확인해야 한다.

### 참고 자료

- [GCC — Builtins for Memory Model Aware Atomic Operations](https://gcc.gnu.org/onlinedocs/gcc/_005f_005fatomic-Builtins.html)
