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

7.9 KiB

id, title, category, status, source_trust_level, verification_status, created_at, updated_at, tags, tech_stack, applied_in, aliases
id title category status source_trust_level verification_status created_at updated_at tags tech_stack applied_in aliases
cs-lockfree-atomic Lock-free / Atomic — Concurrent 자료구조 Coding draft B conceptual 2026-05-09 2026-05-09
cs
concurrency
lockfree
vibe-coding
language applicable_to
Various
Backend
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)

// 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 간 공유

// 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)

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)

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)

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

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

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.
// 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).
#[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

// Strict typing 어려움 — Atomics 가 untyped.
function atomicLoad(view: Int32Array, idx: number): number {
  return Atomics.load(view, idx);
}

Test

// 동시성 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).

🔗 관련 문서