+4

Giới thiệu bộ nhớ chia sẻ trong JavaScript

Bộ nhớ chia sẻ là một tính năng nâng cao của JavaScript. Việc chia sẻ bộ nhớ có nghĩa là nhiều luồng có thể cùng truy cập và cập nhật cùng một dữ liệu trong bộ nhớ chia sẻ.

Trong bài viết này chúng ta sẽ cùng xem cách sử dụng bộ nhớ chia sẻ trong JS.

Ưu & nhược điểm của bộ nhớ chia sẻ

Trong bài viết Giới thiệu API Web Worker trong JavaScript, mình đã giới thiệu đến các bạn việc sử dụng API Web Workers để tạo ra các luồng chạy ngầm trong JS. Với các luồng chạy ngầm như thế thì luồng chính của chúng ta sẽ được giải phóng để tiếp tục thực hiện công việc, người dùng không phải chờ đợi.

Các luồng worker sẽ chạy song song với luồng chính, thực hiện đồng thời các phần của tác vụ, giúp hệ thống chạy nhanh hơn. Tuy nhiên, vì cùng làm việc đồng thời nên nó cũng gặp phải một số vấn đề.

  • Đảm bảo các luồng nhận được tài nguyên hợp lý và giao tiếp với nhau kịp thời không?
  • Sẽ ra sao nếu một luồng đang cập nhật dữ liệu trong khi một luồng khác lại đang đọc chính dữ liệu đó? ...

Thực ra thì Web Workers có một cơ chế hơi khác một chút, đó là trong quá trình giao tiếp với nhau bằng cách gửi message thì dữ liệu được gửi đi chỉ là một bản sao của dữ liệu gốc mà thôi. Có nghĩa là các worker thực sự không chia sẻ cùng một dữ liệu, chúng truyền bản sao của dữ liệu đó cho nhau khi cần thiết.

Vấn đề của chia sẻ bộ nhớ là có thể có nhiều luồng cần đọc cùng một dữ liệu tại cùng một thời điểm và cùng thay đổi dữ liệu đó.

Trong JS có đối tượng SharedArrayBuffer, cho phép chúng ta chia sẻ dữ liệu nhị phân giữa nhiều luồng khác nhau. Hãy cùng tìm hiểu.

Đối tượng SharedArrayBuffer

Thay bằng việc gửi bản sao của dữ liệu giữa các luồng với nhau, chúng ta sẽ gửi bản sao của đối tượng SharedArrayBuffer. Một đối tượng SharedArrayBuffer trỏ đến bộ nhớ, nơi dữ liệu được lưu trữ.

Do đó, ngay cả khi bản sao đối tượng SharedArrayBuffer được truyền đi giữa các luồng thì các luồng vẫn trỏ đến cùng một bộ nhớ, nơi dữ liệu gốc được lưu. Tất cả các luồng cso thể đọc và cập nhật dữ liệu trong cùng một bộ nhớ.

Web Workers làm việc mà không có bộ nhớ chia sẻ

Ta tạo một worker và truyền dữ liệu gủi đến cho nó. Cùng xem ví dụ đơn giản sau:

  • index.html
<script>
  const w = new Worker('worker.js');
  var n = 9;
  w.postMessage(n);
</script>
  • worker.js
onmessage = (e)=>{
  console.group('[worker]');
  console.log('Data received from main thread: %i', e.data);
  console.groupEnd();
}

Với code như trên, khi chạy thì ta in ra console như sau:

[worker]
Data received from main thread: 9

Các bạn có thể xem cụ thể hơn về code ở bài viết này.

Đơn giản, bạn có thể hiểuhiểu là dữ liệu được gửi qua lại giữa các luồng bằng phương thức postMessage() và khi luồng đích nhận dữ liệu sẽ xử lý sự kiện 'message' để nhận dữ liệu đó.

Khi ta thay đổi dữ liệu gửi đi thì luồng đích có nhận được dữ liệu đã thay đổi không? Hãy xem ví dụ sau:

  • index.html
<script>
  const w = new Worker('worker.js');
  var n = 9;
  w.postMessage(n);
  n = 1;
</script>

Kết quả nhận được thì dữ liệu vẫn chưa được thay đổi:

[worker]
Data received from main thread: 9

Bởi vì dữ liệu được luồng chính gửi đi chỉ là một bản sao của dữ liệu gốc mà thôi.

Web Workers làm việc với bộ nhớ chia sẻ

Bây giờ, ta sẽ làm một ví dụ tương tự như phần trên, nhưng sẽ sử dụng đối tượng SharedArrayBuffer.

  • index.html
<script>
  const w = new Worker('worker.js');

  /* create new SharedArrayBuffer object */
  buff = new SharedArrayBuffer(1);
  var arr = new Int8Array(buff);

  /* setting data */
  arr[0] = 9;

  /* sending the buffer (copy) to worker */
  w.postMessage(buff);
</script>

Chú ý rằng mỗi đối tượng SharedArrayBuffer chỉ tương ứng với một vùng bộ nhớ chia sẻ thôi. Để xem và cập nhật dữ liệu, ta cần phải sử dụng một cấu trúc dữ liệu thích hợp (TypedArray hoặc là DataView).

Ở ví dụ này, một đối tượng SharedArrayBuffer được khởi tạo với 1 byte độ dài, sau đó đối tượng Int8Array (một kiểu của TypedArray) được khởi tạo để đặt dữ liệu vào không gian byte được cung cấp.

Với file worker thì xử lý như sau:

  • worker.js
onmessage = (e)=>{
  var arr = new Int8Array(e.data);
  console.group('[worker]');
  console.log('Data received from main thread: %i', arr[0]);
  console.groupEnd();
}

Int8Array cũng có thể được sử dụng trong worker. Hãy cùng xem kết quả:

[worker]
Data received from main thread: 9

Bây giờ, hãy thử thay đổi giá trị giữ liệu trong luồng chính xem thế nào nhé:

  • index.html
<script>
  const w = new Worker('worker.js');
  buff = new SharedArrayBuffer(1);
  var arr = new Int8Array(buff);
  arr[0] = 9;
  w.postMessage(buff);

  /* changing the data */
  arr[0] = 1;
</script>

Kết quả sẽ là dữ liệu worker nhận được đã là dữ liệu được cập nhật:

[worker]
Data received from main thread: 1

Ngược lại, ta cũng muốn mỗi khi worker cập nhật dữ liệu chia sẻ thì ngay trong luồng chính cũng nhận được dữ liệu mới. Với trường hợp đó, code sẽ như sau:

  • worker.js
onmessage = (e)=>{
  var arr = new Int8Array(e.data);
  console.group('[worker]');
  console.log('Data received from main thread: %i', arr[0]);
  console.groupEnd();

  /* changing the data */
  arr[0] = 7;
  /* posting to the main thread */
  postMessage('');
}

Từ worker, ta thực hiện thay đổi dữ liệu bình thường và gửi về postMessage với nội dung rỗng để báo với luồng chính rằng dữ liệu đã được thay đổi.

  • index.html
<script>
  const w = new Worker('worker.js'),
  buff = new SharedArrayBuffer(1);
  var arr = new Int8Array(buff);
  arr[0] = 9;
  w.postMessage(buff);
  arr[0] = 1;

  /* printing the data after the worker has changed it */
  w.onmessage = (e)=>{
    console.group('[main]');
    console.log('Updated data received from worker thread: %i', arr[0]);
    console.groupEnd();
  }
</script>

Kết quả cuối cùng sẽ là:

[worker]
Data received from main thread: 1
[main]
Updated data received from worker thread: 7

Như vậy, dữ liệu chia sẽ đã được cập nhật một lần ở luồng chính, gửi đến worker. Sau đó, dữ liệu lại được thay đổi một lần nữa ở worker và được gửi trả về luồng chính.

Tổng kết

Như đã nói ở trên, việc sử dụng bộ nhớ chia sẻ trong JavaScript ngoài ưu điểm cũng có nhiều nhược điểm khi mà các luồng có xảy ra xung đột khi đồng thay đổi một dữ liệu.

Hi vọng bài viết này sẽ giúp bạn hiểu được những kiến thức cơ bản của chia sẻ bộ nhớ trong JavaScript, và việc kết hợp với các ngôn ngữ khác có thể sẽ giúp bạn cải thiện được hệ thống của mình


Tham khảo


Cám ơn bạn đã theo dõi bài viết!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí