Atomics
Atomics
Atomics 네임스페이스 객체는 원자적(atomic) 연산을 수행하기 위한 정적 메서드를 담고 있다. 이들은 SharedArrayBuffer와 ArrayBuffer 객체와 함께 사용된다.
본문
개요
대부분의 전역 객체와 달리 Atomics는 생성자(constructor)가 아니다. new 연산자와 함께 사용할 수 없으며 Atomics 객체를 함수로 호출할 수도 없다. Atomics의 모든 속성과 메서드는 정적(static)이다(Math 객체와 마찬가지로).
원자적 연산(Atomic operations)
메모리가 공유되면 여러 스레드가 메모리의 같은 데이터를 읽고 쓸 수 있다. 원자적 연산은 예측 가능한 값이 쓰이고 읽히도록 하고, 다음 연산이 시작되기 전에 연산이 끝나도록 하며, 연산이 중단되지 않도록 보장한다.
Wait와 notify
wait()와 notify() 메서드는 Linux futex("fast user-space mutex")를 모델로 하며, 특정 조건이 참이 될 때까지 기다리는 방법을 제공하고, 일반적으로 차단(blocking) 구조로 사용된다.
정적 속성(Static properties)
Atomics[Symbol.toStringTag]—[Symbol.toStringTag]속성의 초기 값은 문자열"Atomics".Object.prototype.toString()에서 사용된다.
정적 메서드(Static methods)
Atomics.add()— 배열의 지정된 인덱스의 기존 값에 제공된 값을 더한다. 그 인덱스의 이전 값을 반환한다.Atomics.and()— 배열의 지정된 인덱스의 값과 제공된 값에 대해 비트 AND를 계산한다. 그 인덱스의 이전 값을 반환한다.Atomics.compareExchange()— 값이 어떤 값과 같으면 배열의 지정된 인덱스에 값을 저장한다. 이전 값을 반환한다.Atomics.exchange()— 배열의 지정된 인덱스에 값을 저장한다. 이전 값을 반환한다.Atomics.isLockFree()— 잠금(lock)을 사용할지 원자 연산을 사용할지 결정하는 데 사용할 수 있는 최적화 프리미티브. 주어진 요소 크기의 배열에 대한 원자 연산이 (잠금이 아닌) 하드웨어 원자 연산으로 구현될 것이면true를 반환한다. 전문가 전용.Atomics.load()— 배열의 지정된 인덱스의 값을 반환한다.Atomics.notify()— 배열의 지정된 인덱스에서 대기 중인 agent들에 알린다. 알림을 받은 agent 수를 반환한다.Atomics.or()— 배열의 지정된 인덱스의 값과 제공된 값에 대해 비트 OR을 계산한다. 그 인덱스의 이전 값을 반환한다.Atomics.pause()— 호출자가 공유 리소스 접근을 기다리며 스핀하고 있음을 CPU에 힌트로 알리는 미세 대기 마이크로-wait 프리미티브를 제공한다. 이를 통해 시스템은 현재 스레드를 양보하지 않고 코어(전력 등)나 스레드에 할당된 리소스를 줄일 수 있다.Atomics.store()— 배열의 지정된 인덱스에 값을 저장한다. 그 값을 반환한다.Atomics.sub()— 배열의 지정된 인덱스에서 값을 뺀다. 그 인덱스의 이전 값을 반환한다.Atomics.wait()— 배열의 지정된 인덱스가 여전히 값을 포함하는지 확인하고, 대기하거나 타임아웃될 때까지 잠든다."ok","not-equal","timed-out"중 하나를 반환한다. 호출하는 agent에서 대기가 허용되지 않으면 예외를 발생시킨다. (대부분의 브라우저는 브라우저의 메인 스레드에서wait()를 허용하지 않는다.)Atomics.waitAsync()— 공유 메모리 위치에서 비동기적으로(Atomics.wait와 달리 차단하지 않고) 대기하며, 연산 결과를 나타내는 객체를 반환한다.Atomics.xor()— 배열의 지정된 인덱스의 값과 제공된 값에 대해 비트 XOR을 계산한다. 그 인덱스의 이전 값을 반환한다.
예제
이 예제들은 SharedArrayBuffer가 보안 요구 사항을 충족하지 않으면 정의되지 않으므로 콘솔이나 임의의 웹 페이지에서 직접 실행할 수 없다는 점을 주의하라.
Atomics 사용하기
const sab = new SharedArrayBuffer(1024);
const ta = new Uint8Array(sab);
ta[0]; // 0
ta[0] = 5; // 5
Atomics.add(ta, 0, 12); // 5
Atomics.load(ta, 0); // 17
Atomics.and(ta, 0, 1); // 17
Atomics.load(ta, 0); // 1
Atomics.compareExchange(ta, 0, 5, 12); // 1
Atomics.load(ta, 0); // 1
Atomics.exchange(ta, 0, 12); // 1
Atomics.load(ta, 0); // 12
Atomics.isLockFree(1); // true
Atomics.isLockFree(2); // true
Atomics.isLockFree(3); // false
Atomics.isLockFree(4); // true
Atomics.or(ta, 0, 1); // 12
Atomics.load(ta, 0); // 13
Atomics.store(ta, 0, 12); // 12
Atomics.sub(ta, 0, 2); // 12
Atomics.load(ta, 0); // 10
Atomics.xor(ta, 0, 1); // 10
Atomics.load(ta, 0); // 11
대기(wait)와 알림(notify)
공유된 Int32Array가 주어진 경우:
const sab = new SharedArrayBuffer(1024);
const int32 = new Int32Array(sab);
읽기 스레드는 제공된 값이 지정된 인덱스에 저장된 값과 일치하므로 위치 0에서 잠들어 대기한다. 읽기 스레드는 쓰기 스레드가 지정된 typed array의 위치 0에서 Atomics.notify()를 호출할 때까지 진행하지 않는다. 깨어난 후에도 쓰기 스레드가 위치 0의 값을 변경하지 않았다면, 읽기 스레드는 다시 잠들지 않고 계속 진행한다는 점에 주의하라.
Atomics.wait(int32, 0, 0);
console.log(int32[0]); // 123
쓰기 스레드는 새 값을 저장하고, 쓰기가 완료되면 대기 중인 스레드에 알린다:
console.log(int32[0]); // 0;
Atomics.store(int32, 0, 123);
Atomics.notify(int32, 0, 1);
명세(Specifications)
- ECMAScript® 2027 Language Specification — sec-atomics-object
브라우저 호환성
baseline 기준 2021년 12월부터 널리 사용 가능하다. 호환성 표는 JavaScript를 활성화해야 볼 수 있다.
참고 자료
ArrayBuffer- JavaScript typed arrays 가이드
- Web Workers
- Shared Memory – a brief tutorial (TC39 ecmascript-sharedmem 제안)
- A Taste of JavaScript's New Parallel Primitives (hacks.mozilla.org, 2016)