자바스크립트의 SharedArrayBuffer
이 문서는 자바스크립트의 SharedArrayBuffer
에 대해 설명합니다.
SharedArrayBuffer
의 기본 개념, 사용 방법, 구체적인 사용 사례, 그리고 보안 관련 사항에 대해 자세히 설명하겠습니다.
YouTube Video
자바스크립트의 SharedArrayBuffer
SharedArrayBuffer
는 여러 스레드 간의 메모리를 공유하기 위한 자바스크립트의 강력한 도구입니다. 특히 Web Workers와 결합하면 병렬 처리가 가능하여 계산 집약적인 작업과 실시간 기능이 필요한 애플리케이션에 효과적입니다.
SharedArrayBuffer란 무엇인가?
SharedArrayBuffer
는 여러 스레드(주로 Web Workers) 간에 이진 데이터를 공유할 수 있는 자바스크립트의 메모리 버퍼를 제공합니다. 일반 ArrayBuffer
는 메인 스레드와 워커 간에 복사가 필요하지만, SharedArrayBuffer
는 복사 없이 직접 메모리 공유가 가능하여 성능을 크게 향상시킵니다.
기능
- 공유 메모리 여러 스레드가 동일한 메모리 공간에서 작업할 수 있게 합니다.
- 성능 향상 복사를 생략할 수 있기 때문에 대량의 데이터를 처리할 때 오버헤드가 줄어듭니다.
- 스레드 동기화
Atomics
와 함께 사용하여 메모리 접근 시 충돌을 방지하기 위한 동기화를 수행할 수 있습니다.
기본 사용 예시
1// Create a 16-byte shared memory
2const sharedBuffer = new SharedArrayBuffer(16);
3
4// Treat it as an Int32Array
5const sharedArray = new Int32Array(sharedBuffer);
6
7// Set a value
8sharedArray[0] = 42;
9
10console.log(sharedArray[0]); // 42
이 예제에서는 SharedArrayBuffer
를 사용하여 16바이트 메모리 영역을 생성하고 해당 메모리 영역을 Int32Array
로 취급합니다. 이 메모리 버퍼는 여러 스레드 간에 공유할 수 있습니다.
Web Workers와 함께 사용하기
SharedArrayBuffer
의 진정한 가치는 Web Workers와 결합하여 사용할 때 나타납니다. 다음 코드는 메인 스레드와 워커 간에 공유 메모리를 사용하는 예제입니다.
메인 스레드에서
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Modify the memory
12// Output : Main thread: 100
13sharedArray[0] = 100;
14console.log("Main thread: ", sharedArray[0]);
워커 측에서 (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 // Use the received shared buffer
4 const sharedArray = new Int32Array(event.data);
5
6 // Read the contents of the memory
7 // Output : Worker thread: 100
8 console.log("Worker thread: ", sharedArray[0]);
9
10 // Change the value
11 sharedArray[0] = 200;
12};
- 이 예제에서는 메인 스레드가 공유 버퍼를 생성하고 이를 워커에 전달합니다. 워커는 이 버퍼에 액세스하여 값을 읽고 수정할 수 있습니다. 이 방식으로 데이터를 복사 없이 스레드 간에 공유할 수 있습니다.
양방향 업데이트 확인
SharedArrayBuffer
를 사용하면 메인 스레드와 워커 모두 동일한 메모리를 읽고 쓸 수 있어 양방향 업데이트 확인이 가능합니다. 아래는 메인 스레드가 값을 설정하고, 워커가 이 값을 변경한 뒤, 메인 스레드가 업데이트를 확인하는 예시입니다.
메인 스레드에서
1// Create a shared buffer
2const sharedBuffer = new SharedArrayBuffer(16);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create a worker
6const worker = new Worker('worker.js');
7
8// Pass the shared buffer to the worker
9worker.postMessage(sharedBuffer);
10
11// Set initial value
12// Output : Main thread initial: 100
13sharedArray[0] = 100;
14console.log("Main thread initial:", sharedArray[0]);
15
16// Listen for worker confirmation
17worker.onmessage = () => {
18 // Output : Main thread after worker update: 200
19 console.log("Main thread after worker update:", sharedArray[0]);
20};
워커 측에서 (worker.js
)
1// worker.js
2self.onmessage = function(event) {
3 const sharedArray = new Int32Array(event.data);
4
5 // Read initial value
6 // Output : Worker thread received: 100
7 console.log("Worker thread received:", sharedArray[0]);
8
9 // Update the value
10 sharedArray[0] = 200;
11
12 // Notify main thread
13 self.postMessage("Value updated");
14};
- 이 예시에서 메인 스레드는 먼저 값
100
을 쓰고, 워커가 이를 읽은 후200
으로 다시 씁니다. 그 후 워커가 메인 스레드에 알리고, 메인 스레드는 공유 메모리를 다시 읽어 업데이트를 확인합니다. 이처럼 알림을 결합함으로써 양방향 업데이트를 확인할 수 있습니다.
Atomics를 사용한 동기화
공유 메모리를 사용할 때는 데이터 경합 조건과 불일치에 주의해야 합니다. 다중 스레드가 동일한 메모리에 동시에 접근하면 충돌이 발생할 수 있습니다. 이를 방지하기 위해 JavaScript는 동기화를 위해 Atomics
객체를 사용합니다.
예를 들어, 다중 스레드에서 안전하게 카운터를 증가시키기 위해 Atomics
를 사용하여 충돌을 방지할 수 있습니다.
1const sharedBuffer = new SharedArrayBuffer(16);
2const sharedArray = new Int32Array(sharedBuffer);
3
4// Increment the counter
5Atomics.add(sharedArray, 0, 1);
6
7console.log(Atomics.load(sharedArray, 0)); // 1
Atomics.add
는 특정 인덱스의 값을 원자적으로 증가시키고 새 값을 반환합니다. 이 작업은 다른 스레드와의 충돌이 없음을 보장합니다. Atomics.load
도 공유 메모리에서 값을 안전하게 읽는 데 사용됩니다.
Atomics.wait
와 Atomics.notify
를 사용한 대기 및 알림
SharedArrayBuffer
를 사용할 때, 워커가 특정 조건이 충족될 때까지 대기하고, 다른 워커가 그 조건을 충족시키면 대기 중인 워커에게 알릴 필요가 있습니다. 이러한 경우 Atomics.wait
와 Atomics.notify
가 유용합니다.
Atomics.wait
는 공유 메모리의 특정 인덱스 값이 변경될 때까지 스레드를 블록하고, Atomics.notify
는 대기 중인 스레드에게 진행할 수 있음을 알립니다. 이를 통해 여러 워커 간에 안전한 대기 및 알림이 가능합니다. 하지만 Atomics.wait
는 메인 스레드에서는 사용할 수 없고, 워커 내부에서만 사용 가능합니다.
1// Create a shared buffer (1 Int32 slot is enough for signaling)
2const sharedBuffer = new SharedArrayBuffer(4);
3const sharedArray = new Int32Array(sharedBuffer);
4
5// Create workers with names
6const waiter = new Worker('worker.js', { name: 'waiter' });
7const notifier = new Worker('worker.js', { name: 'notifier' });
8
9// Pass the shared buffer to both
10waiter.postMessage(sharedBuffer);
11notifier.postMessage(sharedBuffer);
12
13// Listen for messages
14waiter.onmessage = (event) => {
15 console.log(`[Main] Message from waiter:`, event.data);
16};
17notifier.onmessage = (event) => {
18 console.log(`[Main] Message from notifier:`, event.data);
19};
- 메인 스레드에서는 공유 메모리로
SharedArrayBuffer
를 생성하고, 이를 하나의 요소만 가지는Int32Array
로 변환합니다. 이 하나의 정수 슬롯은 워커들 간의 동기화를 위한 신호로 사용됩니다. 다음으로 두 개의 워커를 생성하고, 각각name
속성을 사용하여 역할을 부여합니다:waiter
(대기 역할)와notifier
(알림 역할). 마지막으로 공유 버퍼를 두 워커에 모두 전달하고, 각 워커로부터 전송된 메시지를 받을 수 있도록onmessage
핸들러를 설정합니다.
워커 측에서 (worker.js
)
1// worker.js
2onmessage = (event) => {
3 const sharedArray = new Int32Array(event.data);
4
5 if (self.name === 'waiter') {
6 postMessage('Waiter is waiting...');
7 // Wait until notifier signals index 0
8 Atomics.wait(sharedArray, 0, 0);
9 postMessage('Waiter was notified!');
10 }
11
12 if (self.name === 'notifier') {
13 postMessage('Notifier is preparing...');
14 setTimeout(() => {
15 // Notify waiter after 2 seconds
16 Atomics.store(sharedArray, 0, 1);
17 Atomics.notify(sharedArray, 0, 1);
18 postMessage('Notifier has sent the signal!');
19 }, 2000);
20 }
21};
22// Output
23// [Main] Message from waiter: Waiter is waiting...
24// [Main] Message from notifier: Notifier is preparing...
25// [Main] Message from notifier: Notifier has sent the signal!
26// [Main] Message from waiter: Waiter was notified!
- 이 예제에서
waiter
워커는 인덱스0
의 값이0
인 동안Atomics.wait
를 사용해 대기 상태를 유지합니다. 반면,notifier
워커가Atomics.store
로 값을123
으로 변경하고Atomics.notify
를 호출하면,waiter
워커가 다시 시작되어 변경된 값을 얻을 수 있습니다. 이렇게 하면 스레드 간의 효율적이고 안전한 대기 및 알림을 구현할 수 있습니다.
SharedArrayBuffer의 사용 사례
SharedArrayBuffer
는 특히 다음과 같은 사용 사례에 유용합니다:.
- 실시간 처리 오디오, 비디오 처리나 게임 엔진처럼 낮은 지연 시간과 즉각적인 데이터 공유가 필요한 애플리케이션에 적합합니다.
- 병렬 컴퓨팅
여러 스레드로 대량의 데이터를 동시에 처리할 때,
SharedArrayBuffer
를 사용하면 메모리 복사를 피하고 성능을 높일 수 있습니다. - 머신러닝 데이터 전처리나 추론과 같은 작업을 병렬화하여 효율적인 연산이 가능합니다.
보안 고려 사항
SharedArrayBuffer
는 강력한 기능이지만 보안 위험도 수반합니다. 특히 Spectre
와 같은 부채널 공격에 대한 우려로 인해 지원이 일시적으로 중단되었습니다. 이 취약점을 완화하기 위해 브라우저는 다음과 같은 조치를 구현했습니다:.
- 사이트 격리
SharedArrayBuffer
의 사용을 허용하는 사이트는 다른 사이트와 완전히 격리된 프로세스에서 실행됩니다. - 교차 출처 리소스 정책
SharedArrayBuffer
를 사용하려면Cross-Origin-Opener-Policy
와Cross-Origin-Embedder-Policy
헤더를 올바르게 설정해야 합니다.
예를 들어, 다음과 같은 헤더를 설정하면 SharedArrayBuffer
사용이 가능해집니다:.
1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp
이는 외부 리소스가 현재 콘텐츠를 방해하지 못하도록 하여 보안을 강화합니다.
요약
SharedArrayBuffer
는 다중 스레드 간 메모리를 공유하기 위한 매우 강력한 도구입니다. 이는 성능을 향상시키는 데 필수적인 기술이며, 특히 실시간 처리 및 병렬 컴퓨팅 분야에서 그 효과가 뚜렷하게 나타납니다. 그러나 보안 위험도 수반되므로 올바른 설정과 동기화가 중요합니다.
SharedArrayBuffer
를 활용하면 더욱 고급스럽고 고성능의 웹 애플리케이션을 구축할 수 있습니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.