JavaScript 中的 SharedArrayBuffer

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.waitAtomics.notify 進行等待與通知

在使用 SharedArrayBuffer 時,有些情況下 worker 需要等待某個條件成立,當另一個 worker 滿足條件時,需通知正在等待的 worker。在這種情況下,Atomics.waitAtomics.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 的值是 0waiter worker 便會使用 Atomics.wait 保持在等待狀態。另一方面,當 notifier worker 使用 Atomics.store 將值改為 123 並呼叫 Atomics.notify 時,waiter worker 就會恢復執行並取得更新後的數值。透過這種方式,可以在執行緒之間實現高效率且安全的等待與通知

SharedArrayBuffer 的使用案例

SharedArrayBuffer 尤其適用於以下使用案例:。

  • 即時處理 適用於需要低延遲的應用程式,如音訊與視訊處理或遊戲引擎,資料需在執行緒間即時共享。
  • 平行運算 多執行緒同時處理大量資料時,使用 SharedArrayBuffer 可避免記憶體複製,並提升效能。
  • 機器學習 透過將資料預處理、推論等任務平行化,可以實現高效運算。

安全考量

SharedArrayBuffer 是一項強大的功能,但也帶來安全風險。尤其是對像 Spectre 這樣的側通道攻擊的擔憂,曾一度暫停了對它的支援。為減輕此漏洞,瀏覽器實施了以下措施:。

  • 網站隔離 允許使用 SharedArrayBuffer 的網站將在完全與其他網站隔離的程序中運行。
  • 跨來源資源政策 要使用 SharedArrayBuffer,必須正確設置 Cross-Origin-Opener-PolicyCross-Origin-Embedder-Policy 標頭。

例如,通過如下設置標頭,可以啟用 SharedArrayBuffer 的使用:。

1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp

這樣可以防止外部資源干擾當前內容並增強安全性。

總結

SharedArrayBuffer 是一種非常強大的工具,用於在多個執行緒之間共享記憶體。這是一項提升效能的關鍵技術,特別是在即時處理與平行計算領域中其效果尤為顯著。然而,它也涉及安全風險,因此正確的配置和同步非常重要。

利用 SharedArrayBuffer,你可以打造更先進且高效能的網頁應用程式。

您可以在我們的 YouTube 頻道上使用 Visual Studio Code 來跟隨上述文章一起學習。 請也查看我們的 YouTube 頻道。

YouTube Video