Files
2nd/10_Wiki/Topics/Coding/CS_LockFree_Atomic.md
T
2026-05-09 21:08:02 +09:00

336 lines
7.9 KiB
Markdown

---
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<T> {
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]]