JavaScript 中的 SharedArrayBuffer
本文解釋了 JavaScript 中的 SharedArrayBuffer。
我們將詳細解釋 SharedArrayBuffer 的基本概念、如何使用、具體使用案例以及安全考量。
YouTube Video
JavaScript 中的 SharedArrayBuffer
SharedArrayBuffer 是 JavaScript 中用於在多線程之間共享內存的強大工具。特別是結合 Web Workers 使用時,它可以實現並行處理,對計算密集型任務和需要實時能力的應用程序非常有效。
什麼是 SharedArrayBuffer?
SharedArrayBuffer 在 JavaScript 中提供了一個內存緩衝區,可以在多線程(主要是 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,主執行緒和 worker 都可以對同一塊記憶體進行讀寫,實現雙向的更新確認。以下是一個範例,主執行緒設置一個值,worker 修改該值,然後主執行緒檢查此更新。
在主線程中
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,worker 讀取後再將其修改為200。之後,worker 會通知主執行緒,然後主執行緒再次讀取共享記憶體以確認更新。透過結合通知,能夠實現雙向更新的確認。
使用 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 時,有些情況下 worker 需要等待某個條件成立,當另一個 worker 滿足條件時,需通知正在等待的 worker。在這種情況下,Atomics.wait 和 Atomics.notify 就非常實用。
Atomics.wait 會阻塞當前執行緒,直到共享記憶體中特定索引的值發生變化,而 Atomics.notify 則通知等待的執行緒可以繼續執行。這讓多個 worker 之間可以安全地進行等待與通知。但是,Atomics.wait 無法在主執行緒上使用,只能在 worker 中使用。
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。這個單一整數欄位會作為在 worker 之間同步的訊號。接著建立兩個 worker,並使用name屬性分配角色:waiter(等待者角色)和notifier(通知者角色)。最後,將共享緩衝區傳遞給兩個 worker,並設置onmessage處理程序,以便可以接收每個 worker 發送的訊息。
在工作線程中(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!
- 在這個例子中,只要索引
0的值是0,waiterworker 便會使用Atomics.wait保持在等待狀態。另一方面,當notifierworker 使用Atomics.store將值改為123並呼叫Atomics.notify時,waiterworker 就會恢復執行並取得更新後的數值。透過這種方式,可以在執行緒之間實現高效率且安全的等待與通知。
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,你可以打造更先進且高效能的網頁應用程式。
您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。