SharedArrayBuffer는 메인 스레드와 Web Worker 간에 메모리를 복사 없이 공유하는 API입니다. Atomics는 공유 메모리에서 발생하는 경쟁 조건(Race Condition)을 방지하는 동기화 도구입니다.

일반 ArrayBuffer vs SharedArrayBuffer

JS
// 일반 ArrayBuffer — 전송 시 복사 또는 소유권 이전
const buffer = new ArrayBuffer(1024);
worker.postMessage(buffer, [buffer]); // Transferable: 소유권 이전
console.log(buffer.byteLength); // 0 — 더 이상 접근 불가

// SharedArrayBuffer — 공유! 복사 없음
const sharedBuffer = new SharedArrayBuffer(1024);
worker.postMessage(sharedBuffer); // 복사 없이 공유
console.log(sharedBuffer.byteLength); // 1024 — 여전히 접근 가능

보안 요구 사항 (COOP/COEP)

Spectre 공격 이후 SharedArrayBuffer 사용에는 보안 헤더가 필요합니다.

PLAINTEXT
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
JS
// 브라우저에서 확인
if (typeof SharedArrayBuffer !== "undefined") {
  console.log("SharedArrayBuffer 사용 가능");
}
// self.crossOriginIsolated가 true여야 함
console.log(self.crossOriginIsolated);

기본 공유 메모리 패턴

JS
// 메인 스레드
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 10);
const sharedArray = new Int32Array(sharedBuffer);

// 초기값 설정
sharedArray[0] = 42;

// 워커에 공유
worker.postMessage(sharedBuffer);

// worker.js
self.addEventListener("message", (e) => {
  const sharedArray = new Int32Array(e.data);
  console.log(sharedArray[0]); // 42 — 메인 스레드의 데이터 직접 접근
  sharedArray[0] = 100;        // 메인 스레드에서도 변경 반영
});

경쟁 조건 문제

JS
// 두 워커가 동시에 sharedArray[0]++를 실행하면?
// 읽기 → 증가 → 쓰기가 원자적이지 않으므로
// 값이 손실될 수 있음 (Lost Update)

// sharedArray[0] = 0 일 때
// Worker A: 읽기(0) → 증가(1) → 쓰기(1)
// Worker B: 읽기(0) → 증가(1) → 쓰기(1)
// 결과: 1 (기대값 2)

Atomics — 원자적 연산

JS
const sharedArray = new Int32Array(sharedBuffer);

// 원자적 읽기/쓰기
Atomics.store(sharedArray, 0, 42);     // 원자적 쓰기
const value = Atomics.load(sharedArray, 0); // 원자적 읽기

// 원자적 증가/감소
Atomics.add(sharedArray, 0, 1);  // sharedArray[0] += 1 (원자적)
Atomics.sub(sharedArray, 0, 1);  // sharedArray[0] -= 1 (원자적)

// 원자적 비트 연산
Atomics.and(sharedArray, 0, 0xFF);
Atomics.or(sharedArray, 0, 0x80);
Atomics.xor(sharedArray, 0, 0x01);

// 원자적 교환
const old = Atomics.exchange(sharedArray, 0, 99); // 이전 값 반환

// 원자적 비교 후 교환 (CAS)
Atomics.compareExchange(sharedArray, 0, 99, 100);
// sharedArray[0]이 99이면 100으로 교환, 아니면 변경 없음

Atomics.wait / Atomics.notify — 스레드 동기화

JS
// Worker (대기 측)
const sharedArray = new Int32Array(sharedBuffer);
console.log("데이터 대기 중...");
Atomics.wait(sharedArray, 0, 0); // sharedArray[0]이 0인 동안 대기
console.log("깨어남! 값:", Atomics.load(sharedArray, 0));

// 메인 스레드 (알림 측)
Atomics.store(sharedArray, 0, 42);  // 값 변경
Atomics.notify(sharedArray, 0, 1);  // 대기 중인 워커 1개 깨우기

**주의 **: Atomics.wait은 메인 스레드에서 사용할 수 없습니다 (블로킹 방지).

뮤텍스(Mutex) 구현

JS
class Mutex {
  constructor(sharedArray, index = 0) {
    this.sharedArray = sharedArray;
    this.index = index;
  }

  lock() {
    // 0이면 1로 바꾸고 진행 (잠금 획득)
    while (Atomics.compareExchange(this.sharedArray, this.index, 0, 1) !== 0) {
      // 이미 잠겨있으면 대기
      Atomics.wait(this.sharedArray, this.index, 1);
    }
  }

  unlock() {
    Atomics.store(this.sharedArray, this.index, 0);
    Atomics.notify(this.sharedArray, this.index, 1);
  }
}

// 워커에서 사용
const mutex = new Mutex(new Int32Array(sharedBuffer));

mutex.lock();
// 임계 구역 — 안전하게 공유 데이터 수정
sharedData[1] += 1;
mutex.unlock();

실전 — 병렬 배열 처리

JS
// 메인 스레드
const size = 1_000_000;
const buffer = new SharedArrayBuffer(Float64Array.BYTES_PER_ELEMENT * size);
const array = new Float64Array(buffer);

// 데이터 초기화
for (let i = 0; i < size; i++) array[i] = i;

// 4개 워커에 분할
const workerCount = 4;
const chunkSize = size / workerCount;

for (let i = 0; i < workerCount; i++) {
  const worker = new Worker("compute.js");
  worker.postMessage({
    buffer,
    start: i * chunkSize,
    end: (i + 1) * chunkSize,
  });
}

// compute.js
self.addEventListener("message", (e) => {
  const { buffer, start, end } = e.data;
  const array = new Float64Array(buffer);

  for (let i = start; i < end; i++) {
    array[i] = Math.sqrt(array[i]); // 각 워커가 자기 구간만 처리
  }

  self.postMessage("done");
});

SharedArrayBuffer vs postMessage

항목SharedArrayBufferpostMessage
데이터 복사없음 (공유)있음 (복사)
속도빠름느림 (대용량 시)
동기화Atomics 필요불필요
보안 헤더COOP/COEP 필요불필요
사용 난이도높음낮음

**기억하기 **: SharedArrayBuffer는 Web Worker 간 메모리를 복사 없이 공유하며, Atomics는 경쟁 조건을 방지합니다. COOP/COEP 헤더가 필요하고, 동기화 로직이 복잡하므로 대용량 데이터 처리나 고성능이 필요한 경우에만 사용합니다. 대부분의 경우 postMessage + Transferable로 충분합니다.

댓글 로딩 중...