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