본문 바로가기
Dev/javascript

Spinlock - 스핀락

by 괴발짜응 2025. 2. 28.
반응형

스핀락(Spinlock)이란?

  • 스핀락은 락(잠금) 대상이 해제될 때까지 스레드가 계속해서(바쁘게) 확인하는(스핀 도는)' 방식으로 동작하는 락 구현이다.
  • 보통 멀티스레드 환경에서 하나의 스레드가 잠금을 획득하려고 할 때, 잠금을 사용 중인 다른 스레드가 잠금을 해제할 때까지 짧은 반복문으로 대기(바쁜 대기, busy-wait)하는 구조다.
  • 일반적인 뮤텍스(mutex)는 OS 커널이 스레를 '수면(sleep)' 상태로 만들어 CPU를 양보하지만, 스핀락은 스레드를 그대로 계속 돌면서 잠금 여부를 확인한다.

 

스핀락의 장단점

장점

  • 락을 기다리는 시간이 매우 짧다면(ns ~ μs), 스레드를 커널에 의뢰해 '수면/깨우기(sleep/wakeup)' 과정을 거치는 것보다 빠를 수 있다.
  • 커널에 의존하기보다 사용자 레벨에서 락을 제어하므로 컨텍스트 스위칭 비용이 적을 때 이점이 있다.

단점

  • 바쁘게 도는 만큼 CPU 자원을 계속 사용한다(= 효율이 떨어짐).
  • 락 경쟁이 심하면 CPU 부담이 커지고, 다른 작업이 지연될 수 있다.
  • 장시간 잠금이 필요한 상황에서는 오히려 커널 락(뮤텍스) 같은 방식을 쓰는 편이 낫다.

자바스크립트에서 스핀락은 왜 특별한가?

자바스크립트(브라우저 메인 스레드)는 전통적으로 싱글 스레드 + 이벤트 루프 모델을 사용하기 때문에 "바쁘게 도는" 코드를 작성하면 이벤트 루프가 막혀서 다른 이벤트를 처리할 수 없게 된다. 즈거, 스피란의 의미가 거의 없어진다.

다만, Web Worker(또는 Node.js Worker Threads) 와 SharedArrayBuffer + Atomics를 사용하면, 멀티스레드와 유사한 구조에서 공유 메모리에 접근이 가능하다. 이때, Worker 간 자원을 잠금할 필요가 있을 수 있는데, 그런 상황에서 이론적으로 스핀락을 구현할 수 있다.

 

주의: 브라우저 환경에서 SharedArrayBuffer를 사용하려면, COOP/COEP 등의 보안 헤더가 설정되어 있어야 한다. 보통크롬, 파이어폭스, 사파리 등에서는 SharedArrayBuffer를 기본적으로 차단하기 때문이다. 

 

1. 간단한 자바스크립트 스핀락 구현

1.1 SharedArrayBuffer와 Atomics를 이용한 예시

SharedArrayBuffer 생성 및 공유 배열 뷰 생성

// lockArray는 길이 1짜리 Int32Array
// 0 -> unlocked, 1 -> locked
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
const lockArray = new Int32Array(sharedBuffer);
lockArray[0] = 0; // 초기 상태 unlocked

 

스핀락 함수 구현

function spliLock(lockArray) {
  // Atomics.compareExchange(배열, 인덱스, 예상값, 바꿀 값)
  // lockArray[0]이 0이면(=unlocked이면) 1로 바꾸고, 이전 값을 반환(0)
  // lockArray[0]이 이미 1이면(=locked), compareExchange는 1을 반환
  while (Atomics.compareExchange(lockArray, 0, 0, 1) !== 0) {
   // 잠금이 풀릴 때까지 바쁘게 대기(spin)
   // 아주 짧은 대기나, Atomics.wait 등을 활용할 수도 있음
  }
}

function spinUnlock(lockArray) {
  // 락 해제
  Atomics.store(lockArray, 0, 0);
}

 

Worker간 사용 예시

예시로 메인 스레드와 하나의 워커가 있다고 가정. 워커에 sharedBuffer 를 전달하고, 내부에서 spinLock(lockArray) -> 공유 데이터 수정 -> spinUnlock(lockArray) 과 같이 사용할 수 있다.

// main.js
const sharedBuffer = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
const sharedView = new Int32Array(sharedBuffer);

const worker = new Worker('worker.js');
worker.postMessage({ sharedBuffer });

// 메인에서 사용 예
spinLock(lockArray);
// 공유 데이터 갱신
doSomething();
spinUnlock(lockArray);
// worker.js
onmessage = (e) => {
  const { sharedBuffer } = e.data;
  const lockArray = new Int32Array(sharedBuffer);
  
  // worker에서도 동일하게 사용
  spinLock(lockArray);
  // 공유 데이터 갱신
  doSomething();
  spinUplock(lockArray);
};

function spinLock(lockArray) {
  while (Atomics.compareExchange(lockArray, 0, 0, 1) !== 0) {}
}

function spinUnlock(lockArray) {
  Atomics.store(lockArray, 0, 0);
}

 

1.2 Atomics.wait/notify로 개선하기

단순 무한 푸르(while)로 스핀을 돌리면 CPU가 계속 소비된다. Atomics.wait() (Node.js, 브라우저에서 WebAssembly Threads가 활성화된 환경 등 지원 필요)와 Atomics.notify()를 이용하면, OS 차원의 block/wakeup과 비슷한 구조로 구현이 가능하다. 이 방식 전통적인 "뮤텍스 + 조건 변수" 구조에 가깝다.

function lock(lockArray) {
  while (true) {
    // 아직 잠겨있지 않다면(0) -> 1로 만들어 락 획득 시도
    if (Atomics.compareExchange(lockArray, 0, 0, 1) === 0) {
      // 잠금 성공
      return;
    }
    // 잠금에 실패하면 현재 스레드는 대기
    Atomics.wait(lockArray, 0, 1);
  }
}

function unlock(lockArray) {
  // 1 -> 0 (잠금 해제)
  Atomics.store(lockArray, 0, 0);
  
  // 잠금 대기 중인 다른 스레드를 깨움
  Atomics.notify(lockArray, 0, 1);
}

이렇게 하면 무조건 busy-wait 하지 않고, 잠금이 걸려있는 동안에는 Atomics.wait()를 통해 일시 정지 상태가 되었다가, 잠금 해제 시점에 Atomics.notify() 호출로 깨울 수 있다(단, 모든 브라우저에서 완벽히 지원되는 것은 아니므로 호환성을 확인야 한다).


스핀락 vs. 다른 동기화 방안

  • 자바스크립트에서 머티스레드를 다루는 경우, 스핀락보다는 일반적으로 Atomics.wait/notify나 MessageChaneel, postMessage 등을 통한 Worker 간 통신으로 문제를 해결하는 편이 많다.
  • 브라우저는 전통적인 POSIX 스레드 환경과 다르고, 메인 스레드에서는 특히 스핀락을 사용하면 UI나 이벤트 루프가 완전히 멈춘다.
  • Node.je Worker Threads 환경 역시, Node.js 자체에서 제공하는 MessagePort나 다른 비동기 I/O 구조를 사용하는 편이 더 낫다.
  • 스핀락은 매우 짧은 시간( 몇 마이크로초 이하) 동안 잠금이 필요한 극히 제한된 상황에서만 성능상의 이점이 있을 수 있다.(그 밖에는 OS 스케쥴링/뮤텍나 Atomics.wait/notify 쪽이 CPU 낭비를 줄이는 데 유리)

정리

  • 스핀락은 여러 스레드가 동시에 하나의 공유 자원에 접근할 때, 락이 해제될 때까지 바쁘게 돌면서 확인하는 방식이다.
  • 자바스크립트는 원래 싱글 스레드이므로, 전통적 의미의 스핀락이 메인 스레드에서 쓰이기는 어렵다(이벤트 루프를 차단하기 때문).
  • SharedArraybuffer + Atomics (그리고 Worker Threads)를 이용하면 멀티스레드 유사 환경에서 스핀락을 구현할 수 있다.
  • 그러나 CPU 점유가 매우 커질 수 있으므로, 실제로는 Atomics.wait/notify 또는 메시지 기반의 동기화 기법을 더 권장한다.

 

결론

자바스크립트에서 스핀락은 특정 멀티스레드 상황(Worker + SharedArrayBuffer)에서만 의미가 있으며, 대부분의 웹 애플리케이션에서는 부적합하거나 다른 동기화 방식이 더 효율적이다.

 


반응형

'Dev > javascript' 카테고리의 다른 글

Express 5.0 및 5.1의 주요 변경 사항  (0) 2025.04.03
Atomics?  (1) 2025.03.11
SharedArrayBuffer  (1) 2025.02.28
CommonJS, AMD, UMD, ESM  (1) 2025.02.24
ECMAScript 2024 : ES2024 - 주요 추가 기능  (0) 2025.02.22