SharedArrayBuffer trong JavaScript

SharedArrayBuffer trong JavaScript

Bài viết này giải thích về SharedArrayBuffer trong JavaScript.

Chúng tôi sẽ cung cấp một giải thích chi tiết về những điều cơ bản của SharedArrayBuffer, cách sử dụng nó, các trường hợp sử dụng cụ thể và các cân nhắc về bảo mật.

YouTube Video

SharedArrayBuffer trong JavaScript

SharedArrayBuffer là một công cụ mạnh mẽ trong JavaScript để chia sẻ bộ nhớ giữa các luồng. Đặc biệt khi kết hợp với Web Workers, nó cho phép xử lý song song, làm cho nó hiệu quả cho các tác vụ tính toán nặng và các ứng dụng yêu cầu khả năng xử lý theo thời gian thực.

SharedArrayBuffer là gì?

SharedArrayBuffer cung cấp một bộ nhớ đệm trong JavaScript cho phép chia sẻ dữ liệu nhị phân giữa nhiều luồng (chủ yếu là Web Workers). Một ArrayBuffer thông thường yêu cầu sao chép giữa luồng chính và các worker, nhưng SharedArrayBuffer cho phép chia sẻ bộ nhớ trực tiếp mà không cần sao chép, qua đó cải thiện đáng kể hiệu suất.

Đặc điểm

  • Bộ nhớ dùng chung Nó cho phép nhiều luồng cùng làm việc trên một vùng nhớ chung.
  • Cải thiện hiệu suất Vì có thể bỏ qua quá trình sao chép, chi phí xử lý được giảm đáng kể khi xử lý lượng dữ liệu lớn.
  • Đồng bộ hóa luồng Bạn có thể sử dụng nó kết hợp với Atomics để đồng bộ hóa nhằm tránh xảy ra xung đột khi truy cập bộ nhớ.

Ví dụ sử dụng cơ bản

 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

Trong ví dụ này, chúng ta tạo một khu vực bộ nhớ 16 byte bằng SharedArrayBuffer và xử lý khu vực bộ nhớ đó như một Int32Array. Bộ nhớ đệm này có thể được chia sẻ giữa nhiều luồng.

Sử dụng với Web Workers

Giá trị thực sự của SharedArrayBuffer được thể hiện khi sử dụng cùng với Web Workers. Đoạn code sau đây là một ví dụ về cách sử dụng bộ nhớ chia sẻ giữa luồng chính và một worker.

Trên luồng chính

 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]);

Trong Worker (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};
  • Trong ví dụ này, luồng chính tạo ra một bộ đệm chia sẻ và truyền nó tới worker. Worker có thể truy cập bộ đệm này để đọc và sửa đổi các giá trị. Bằng cách này, dữ liệu có thể được chia sẻ giữa các luồng mà không cần sao chép.

Xác nhận cập nhật hai chiều

Bằng cách sử dụng SharedArrayBuffer, cả luồng chính và các worker đều có thể đọc và ghi vào cùng một vùng nhớ, qua đó cho phép xác nhận cập nhật hai chiều. Dưới đây là ví dụ trong đó luồng chính thiết lập một giá trị, worker thay đổi giá trị đó, sau đó luồng chính kiểm tra sự cập nhật.

Trên luồng chính

 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};

Trong Worker (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};
  • Trong ví dụ này, trước tiên luồng chính ghi giá trị 100, sau khi worker đọc được giá trị này thì nó sẽ ghi đè lại thành 200. Sau đó, worker sẽ thông báo cho luồng chính, và luồng chính sẽ đọc lại bộ nhớ dùng chung để xác nhận cập nhật. Bằng cách kết hợp việc thông báo, ta có thể xác nhận cập nhật hai chiều.

Đồng bộ hóa với Atomics

Khi sử dụng bộ nhớ chia sẻ, cần thận trọng với các điều kiện đua dữ liệu và sự không nhất quán. Khi nhiều luồng truy cập cùng một bộ nhớ đồng thời, xung đột có thể xảy ra. Để ngăn chặn điều này, JavaScript sử dụng đối tượng Atomics để đồng bộ hóa.

Ví dụ, để tăng an toàn một bộ đếm bằng nhiều luồng, bạn có thể sử dụng Atomics để tránh xung đột.

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 tăng giá trị tại một chỉ số cụ thể một cách nguyên tử và trả về giá trị mới. Hoạt động này được đảm bảo không có xung đột với các luồng khác. Atomics.load cũng được sử dụng để đọc giá trị một cách an toàn từ bộ nhớ chia sẻ.

Chờ đợi và thông báo bằng Atomics.waitAtomics.notify

Khi sử dụng SharedArrayBuffer, có những trường hợp worker cần phải chờ đến khi một điều kiện nào đó được đáp ứng, và khi worker khác hoàn thành điều kiện đó thì phải thông báo cho worker đang chờ. Trong trường hợp như vậy, Atomics.waitAtomics.notify rất hữu ích.

Atomics.wait sẽ chặn một luồng cho đến khi giá trị tại chỉ số xác định trong bộ nhớ dùng chung thay đổi, còn Atomics.notify sẽ thông báo cho các luồng đang chờ rằng họ có thể tiếp tục. Điều này giúp các worker có thể chờ đợi và thông báo an toàn với nhau. Tuy nhiên, Atomics.wait không thể được sử dụng trên luồng chính và chỉ có sẵn trong các 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};
  • Trên luồng chính, một SharedArrayBuffer được tạo ra như bộ nhớ chia sẻ và được chuyển đổi thành một Int32Array chỉ với một phần tử. Khe số nguyên duy nhất này được sử dụng như một tín hiệu để đồng bộ hóa giữa các worker. Tiếp theo, hai worker được tạo ra và mỗi người được gán một vai trò sử dụng thuộc tính name: waiter (vai trò chờ) và notifier (vai trò thông báo). Cuối cùng, bộ đệm chia sẻ được chuyển đến cả hai worker và các trình xử lý onmessage được thiết lập để các thông điệp gửi từ mỗi worker có thể được nhận.

Trong 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!
  • Trong ví dụ này, worker waiter sẽ ở trạng thái chờ đợi sử dụng Atomics.wait miễn là giá trị tại chỉ số 00. Mặt khác, khi worker notifier thay đổi giá trị thành 123 bằng Atomics.store và gọi Atomics.notify, worker waiter sẽ tiếp tục và có thể lấy được giá trị đã cập nhật. Với điều này, có thể đạt được việc chờ đợi và thông báo giữa các luồng một cách hiệu quả và an toàn.

Các trường hợp sử dụng của SharedArrayBuffer

SharedArrayBuffer đặc biệt hữu ích trong các trường hợp sử dụng sau:.

  • Xử lý thời gian thực Nó phù hợp cho các ứng dụng yêu cầu độ trễ thấp như xử lý âm thanh, video hoặc game engine, nơi dữ liệu cần được chia sẻ lập tức giữa các luồng.
  • Tính toán song song Khi xử lý lượng dữ liệu lớn đồng thời với nhiều luồng, việc sử dụng SharedArrayBuffer giúp tránh sao chép bộ nhớ và có thể cải thiện hiệu suất.
  • Học máy (Machine Learning) Bằng cách song song hóa các công việc như tiền xử lý dữ liệu và suy luận, việc tính toán trở nên hiệu quả hơn.

Các cân nhắc về bảo mật

SharedArrayBuffer là một tính năng mạnh mẽ, nhưng nó cũng mang theo những rủi ro bảo mật. Cụ thể, lo ngại về các cuộc tấn công kênh bên như Spectre đã tạm thời dừng việc hỗ trợ tính năng này. Để giảm bớt lỗ hổng này, các trình duyệt đã triển khai các biện pháp sau:.

  • Cô lập trang web (Site Isolation) Các trang web cho phép sử dụng SharedArrayBuffer sẽ chạy trong một tiến trình hoàn toàn tách biệt với các trang web khác.
  • Chính sách tài nguyên đa nguồn gốc (Cross-Origin Resource Policy) Để sử dụng SharedArrayBuffer, bạn cần thiết lập đúng các header Cross-Origin-Opener-PolicyCross-Origin-Embedder-Policy.

Ví dụ, bằng cách thiết lập các tiêu đề như sau, việc sử dụng SharedArrayBuffer sẽ trở nên khả thi:.

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

Điều này ngăn chặn tài nguyên bên ngoài can thiệp vào nội dung hiện tại và tăng cường bảo mật.

Tóm tắt

SharedArrayBuffer là một công cụ rất mạnh mẽ để chia sẻ bộ nhớ giữa nhiều luồng. Đây là một công nghệ thiết yếu để cải thiện hiệu suất, và hiệu quả của nó đặc biệt rõ ràng trong các lĩnh vực xử lý thời gian thực và tính toán song song. Tuy nhiên, nó cũng liên quan đến các rủi ro bảo mật, vì vậy cấu hình và đồng bộ hóa chính xác rất quan trọng.

Bằng cách sử dụng SharedArrayBuffer, bạn có thể xây dựng các ứng dụng web tiên tiến hơn và hiệu suất cao hơn.

Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.

YouTube Video