🔒 C11 _Atomic vs Objective-C @property(atomic)
— 같은 “atomic”이지만 전혀 다른 세계
멀티스레드 환경에서 “데이터 경쟁(race condition)”을 막기 위해 우리는 종종 atomic이라는 단어를 접합니다.
하지만 C11의 _Atomic 과 Objective-C의 @property(atomic) 은 이름만 같을 뿐, 실제 동작 원리와 목적은 완전히 다릅니다.
이 글에서는 두 개념의 기술적 차이를 Swift 개발자의 관점에서 명확히 정리해봅니다.
1️⃣ _Atomic — C11의 하드웨어 수준 원자 연산
C11 표준에서 _Atomic은 변수의 읽기/쓰기 연산을 원자적으로 보장하는 타입 수식자입니다.
즉, 해당 변수에 대한 접근이 CPU 명령어 단위로 쪼개지지 않도록 보장합니다.
#include <stdatomic.h>
_Atomic int counter = 0;
void increment() {
atomic_fetch_add(&counter, 1);
}
여러 스레드가 동시에 increment()를 호출해도 안전합니다. atomic_fetch_add는 CPU의 lock prefix를 사용해 “한 번에 읽고, 더하고, 저장”을 처리하기 때문이죠.
- 락(mutex) 없이도 스레드 안전
- 매우 낮은 오버헤드
- compare_exchange를 통해 상태 전이(CAS) 구현 가능
int expected = 0;
atomic_compare_exchange_strong(&state, &expected, 1);
“현재 상태가 0이면 1로 바꾸고, 아니면 그대로 둔다.”
이 한 줄이 완전한 스레드 안전 전이를 수행합니다.
2️⃣ @property(atomic) — Objective-C의 getter/setter 잠금
Objective-C의 @property(atomic)은 완전히 다른 개념입니다.
이는 getter/setter 호출을 잠깐 잠그는 수준의 thread-safety를 제공합니다.
@property (atomic, assign) NSInteger value;
컴파일러는 내부적으로 아래와 유사한 코드를 생성합니다.
- (NSInteger)value {
@synchronized(self) {
return _value;
}
}
즉, 접근자 단위로 @synchronized 락을 걸어주는 것입니다.
따라서 읽기/쓰기 각각은 안전하지만, 읽고 수정하는 복합 연산에서는 여전히 경쟁 상태가 발생합니다.
if (obj.value == 1) { // Thread A가 읽는 중
obj.value = 2; // Thread B가 동시에 수정할 수 있음 ❌
}
@property(atomic)은 “보이기만 atomic한 것”이지, 진정한 원자성(atomicity)을 제공하지 않습니다.
3️⃣ 기술적 차이 요약
| 구분 | _Atomic (C11) | @property(atomic) (Objective-C) |
| 소속 | C 언어 표준 | Objective-C 런타임 |
| 동작 수준 | 하드웨어(CPU) 명령어 단위 | 런타임 수준의 잠금 |
| 구현 방식 | lock cmpxchg 등 CPU 원자 명령 | 내부 @synchronized 블록 |
| 성능 | 매우 빠름 (락 없음) | 느림 (락 오버헤드) |
| 보호 범위 | 개별 연산 단위 | getter/setter 단위 |
| 복합 연산 안전성 | ✅ 완전 보장 (CAS) | ❌ 불가능 |
| Swift 사용 | C 브리징 필요 | Objective-C 전용 |
4️⃣ Swift에서 _Atomic을 활용하는 방법
Swift는 _Atomic을 직접 지원하지 않기 때문에, C11 코드를 래핑해서 사용하는 방식이 일반적입니다.
// AtomicState.h
typedef struct {
_Atomic int v;
} atomic_state_t;
bool atomic_state_try_transition(atomic_state_t *s, int expected, int desired);
// AtomicState.swift
final class AtomicState {
private var raw = atomic_state_t()
func tryTransition(from: Int, to: Int) -> Bool {
atomic_state_try_transition(&raw, from, to)
}
}
또는 Swift 패키지 swift-atomics를 사용하면
C 코드 없이 Swift-native 방식으로 구현할 수도 있습니다.
import Atomics
let counter = ManagedAtomic(0)
counter.wrappingIncrement(ordering: .relaxed)
5️⃣ 결론: “진짜 atomic”과 “겉보기 atomic”의 구분
| 항목 | _Atomic | @property(atomic) |
| 실질적 원자성 | ✅ 있음 | ❌ 없음 |
| 락 사용 여부 | ❌ 없음 | ✅ 있음 |
| 스레드 안전성 | 하드웨어 보장 | getter/setter 한정 |
| 복합 연산 | CAS로 안전하게 처리 | Race 가능성 존재 |
정리하자면:
_Atomic은 CPU가 직접 보장하는 진짜 원자성이고,@property(atomic)은 getter/setter만 잠그는 겉보기 원자성입니다.
💬 마무리
C11의 _Atomic은 Lock-Free 병렬 프로그래밍의 핵심 도구입니다.
Swift 개발자라도 상태 전이, CAS 기반 동기화, 락 없는 상태 머신을 구현할 때 반드시 이해해야 할 개념입니다.
Objective-C의 @property(atomic)은 단순 접근 보호 수준에 불과하므로,
진정한 병렬 안정성을 원한다면 _Atomic 또는 swift-atomics를 사용하는 것이 바람직합니다.
Thread Safety는 복잡한 기술이 아니라, 올바른 도구 선택의 문제다.