[G1-Sync] Manual knowledge update
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
---
|
||||
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]]
|
||||
Reference in New Issue
Block a user