--- id: cs-lockfree-atomic title: Lock-free / Atomic — Concurrent 자료구조 category: Coding status: draft source_trust_level: B verification_status: conceptual created_at: 2026-05-09 updated_at: 2026-05-09 tags: [cs, concurrency, lockfree, vibe-coding] tech_stack: { language: "Various", applicable_to: ["Backend"] } applied_in: [] aliases: [lock-free, atomic, CAS, compare-and-swap, SharedArrayBuffer, Atomics, memory barrier] --- # Lock-free / Atomic > Lock 없이 동시성. **CAS (compare-and-swap), atomic ops, memory barrier**. 빠름 but 어려움. JS = SharedArrayBuffer + Atomics. ## 📖 핵심 개념 - Atomic: 한 instruction 이 indivisible. - CAS: "예상값과 같으면 새 값으로" 한 step. - Memory ordering: read/write reorder 제어. - Lock-free: thread 가 block 안 됨. ## 💻 코드 패턴 ### JS Atomics (SharedArrayBuffer) ```ts // Setup — Cross-Origin Isolation 필요 // Headers: // Cross-Origin-Opener-Policy: same-origin // Cross-Origin-Embedder-Policy: require-corp const sab = new SharedArrayBuffer(1024); const view = new Int32Array(sab); // Atomic ops Atomics.store(view, 0, 42); const v = Atomics.load(view, 0); Atomics.add(view, 0, 1); // atomic increment Atomics.compareExchange(view, 0, 42, 99); // CAS // Wait / notify (futex-like) Atomics.wait(view, 0, 0); // 0 일 때 block Atomics.notify(view, 0, 1); // 1 thread 깨움 ``` ### Worker 간 공유 ```ts // main.ts const sab = new SharedArrayBuffer(1024); const counter = new Int32Array(sab); const worker = new Worker(new URL('./worker.ts', import.meta.url)); worker.postMessage({ sab }); // counter 변경 Atomics.add(counter, 0, 1); // worker.ts self.onmessage = (e) => { const { sab } = e.data; const counter = new Int32Array(sab); setInterval(() => { const v = Atomics.load(counter, 0); console.log('counter:', v); }, 100); }; ``` ### CAS 패턴 (lock-free counter) ```ts function atomicIncrement(view: Int32Array, idx: number): number { while (true) { const cur = Atomics.load(view, idx); const next = cur + 1; if (Atomics.compareExchange(view, idx, cur, next) === cur) { return next; } // 다른 thread 가 변경 — retry } } // 또는 더 단순 — Atomics.add (atomic 자동) const next = Atomics.add(view, idx, 1) + 1; ``` ### Lock-free queue (single-producer, single-consumer) ```ts class SPSCQueue { private buffer: T[]; private head = new Int32Array(new SharedArrayBuffer(4)); private tail = new Int32Array(new SharedArrayBuffer(4)); private capacity: number; constructor(cap: number) { this.capacity = cap; this.buffer = new Array(cap); } push(item: T): boolean { const t = Atomics.load(this.tail, 0); const next = (t + 1) % this.capacity; if (next === Atomics.load(this.head, 0)) return false; // full this.buffer[t] = item; Atomics.store(this.tail, 0, next); return true; } pop(): T | null { const h = Atomics.load(this.head, 0); if (h === Atomics.load(this.tail, 0)) return null; // empty const item = this.buffer[h]; Atomics.store(this.head, 0, (h + 1) % this.capacity); return item; } } ``` ### Mutex (Atomics) ```ts const lockState = new Int32Array(new SharedArrayBuffer(4)); const UNLOCKED = 0; const LOCKED = 1; function lock() { while (Atomics.compareExchange(lockState, 0, UNLOCKED, LOCKED) !== UNLOCKED) { Atomics.wait(lockState, 0, LOCKED); } } function unlock() { Atomics.store(lockState, 0, UNLOCKED); Atomics.notify(lockState, 0, 1); } // 사용 lock(); try { // critical section } finally { unlock(); } ``` → Spinlock + futex. ### Memory ordering ``` JS Atomics = sequential consistency (가장 강). 다른 언어 (C++, Rust) = 더 weak ordering 가능 (relaxed, acquire, release). Acquire/release: synchronization point. Relaxed: ordering 보장 X — 빠름 but careful. ``` ### Rust atomic ```rust use std::sync::atomic::{AtomicI32, Ordering}; let counter = AtomicI32::new(0); counter.fetch_add(1, Ordering::SeqCst); counter.compare_exchange(0, 42, Ordering::SeqCst, Ordering::SeqCst); // 더 weak — 빠름 but careful counter.fetch_add(1, Ordering::Relaxed); ``` ### Java AtomicInteger ```java AtomicInteger counter = new AtomicInteger(0); counter.incrementAndGet(); counter.compareAndSet(0, 42); ``` ### Lock vs Lock-free 결정 ``` Lock: + 단순 + Fairness - Contention 시 thread block - Priority inversion - Deadlock 가능 Lock-free: + Thread block 없음 + 더 빠름 (low contention) + Deadlock 없음 - 어려움 - 더 어려운 디버깅 - ABA problem ``` ### ABA problem ``` T1: read A, prepare to CAS A → C T2: A → B, B → A T1: CAS succeeds (A 가 같다고) — but state 변경됐음 해결: version counter (double-CAS) 또는 hazard pointer. ``` ```ts // Versioned pointer const ptr = new Int32Array(new SharedArrayBuffer(8)); // ptr[0] = value, ptr[1] = version function update(newValue: number) { while (true) { const oldValue = Atomics.load(ptr, 0); const oldVersion = Atomics.load(ptr, 1); if (Atomics.compareExchange(ptr, 1, oldVersion, oldVersion + 1) === oldVersion) { Atomics.store(ptr, 0, newValue); return; } } } ``` ### False sharing ``` 같은 cache line 의 다른 변수 — 한 쪽 쓰기 = 다른 쪽 cache invalidate. → 큰 latency. 해결: padding (cache line 64 byte align). ``` ```rust #[repr(align(64))] struct PaddedCounter { value: AtomicI32, _pad: [u8; 60], } ``` → JS 는 control 어려움. ### Use case ``` - Performance counter (atomic increment) - Real-time game state share - Lock-free queue (audio / video stream) - Reference counting - Wait-free read mostly data ``` ### Anti use case (lock 가 낫음) ``` - 복잡 데이터 구조 - 적은 contention (lock 빠름) - 단순 작업 — 가독성 우선 - Test / debug 어려움 ``` ### Higher-level patterns ``` - Read-Copy-Update (RCU): 읽기 다중, 변경 시 새 copy. - Hazard pointer: ABA 안전 free. - Crossbeam (Rust): high-quality lock-free. - Java java.util.concurrent: thread-safe collections. ``` ### JS — lock-free 제한 ``` JS 는 single-threaded except Worker. Worker 간만 SharedArrayBuffer 가능. Cross-Origin Isolation 필요. → 보통 lock-free 의 use case 적음. AudioWorklet / WebGPU compute = 강. ``` ### TypeScript types ```ts // Strict typing 어려움 — Atomics 가 untyped. function atomicLoad(view: Int32Array, idx: number): number { return Atomics.load(view, idx); } ``` ### Test ```ts // 동시성 test 어려움. Stress test: const N_WORKERS = 4; const N_OPS = 100000; const counter = new Int32Array(new SharedArrayBuffer(4)); const promises = Array.from({ length: N_WORKERS }, () => { return new Promise((resolve) => { const w = new Worker(new URL('./incrementer.ts', import.meta.url)); w.postMessage({ counter, ops: N_OPS }); w.onmessage = resolve; }); }); await Promise.all(promises); const expected = N_WORKERS * N_OPS; const actual = Atomics.load(counter, 0); expect(actual).toBe(expected); ``` ## 🤔 의사결정 기준 | 상황 | 추천 | |---|---| | 단순 counter | Atomic | | Single producer/consumer | Lock-free SPSC queue | | Multi producer | Lock 또는 lock-free MPMC (어려움) | | Read-mostly | RCU / version | | 복잡 자료구조 | Lock | | Real-time audio | Lock-free 필수 | | JS web worker | SharedArrayBuffer + Atomics | ## ❌ 안티패턴 - **Lock-free 무 측정**: 실제 슬로우 가능. - **ABA 무관심**: subtle bug. - **Memory ordering 무지**: data race. - **Spinlock 큰 contention**: CPU 폭발. - **JS Atomics + COOP/COEP 없음**: undefined. - **False sharing 무 padding**: cache thrash. - **Race condition test 없음**: 가끔 fail. ## 🤖 LLM 활용 힌트 - 일반 = mutex. - Hot counter = atomic. - Worker 통신 = SharedArrayBuffer + Atomics. - Memory ordering 알기 (대부분 SeqCst OK). ## 🔗 관련 문서 - [[Web_OffMain_WebWorker]] - [[CS_Cache_Eviction]] - [[CS_Backpressure_Deep]]