Helpex - Trao đổi & giúp đỡ Đăng nhập

WebAssembly: Nhân viên web

Đây là phần tiếp theo của một loạt các bài viết khám phá cách chúng tôi có thể xây dựng và làm việc với các mô-đun WebAssembly bằng cách sử dụng Emscripten. Các bài viết trước không bắt buộc phải đọc để hiểu những gì chúng ta sẽ đề cập ngày hôm nay, nhưng nếu bạn tò mò, bạn có thể tìm thấy chúng tại đây:

  • Giới thiệu về WebAssembly  (bài viết này sử dụng các phương pháp trợ giúp của Emscripten để giao tiếp giữa JavaScript và mô-đun)
  • Sử dụng Emscripten để tạo mô-đun Bare-Bones
  • Gọi vào JavaScript từ mã C Bare-Bones
  • Lưu vào bộ nhớ đệm HTML5 IndexedDB

Hôm nay, chúng ta sẽ tiếp tục sử dụng mô-đun WebAssembly đơn giản (không có phương thức trợ giúp tích hợp sẵn trong Emscripten) chỉ để giữ cho mọi thứ rõ ràng nhất có thể khi chúng ta kiểm tra HTML5 Web worker.

Web Worker cho phép mã JavaScript chạy trong một chuỗi riêng biệt từ chuỗi giao diện người dùng của cửa sổ trình duyệt. Điều này cho phép các tập lệnh chạy dài thực hiện quá trình xử lý của chúng mà không ảnh hưởng đến khả năng phản hồi của giao diện người dùng của trình duyệt.

Vì nhân viên web đang chạy trong một chuỗi riêng biệt với giao diện người dùng, các chuỗi công nhân không có quyền truy cập vào DOM hoặc chức năng giao diện người dùng khác. Ngoài ra, Web worker không nhằm mục đích sử dụng với số lượng lớn và dự kiến ​​sẽ tồn tại lâu dài vì chúng có chi phí hiệu suất khởi động cao và chi phí bộ nhớ cao cho mỗi phiên bản worker.

Mô-đun WebAssembly có thể được tải trong chuỗi giao diện người dùng hoặc trong Web Worker. Sau đó, bạn sẽ lấy mô-đun đã biên dịch và chuyển nó sang một luồng khác, hoặc thậm chí một cửa sổ khác, bằng cách sử dụng phương thức postMessage .

Vì bạn sẽ chỉ chuyển mô-đun đã biên dịch sang luồng khác, bạn có thể gọi WebAssembly.compile thay vì WebAssembly.instantiate để chỉ nhận mô-đun.

Sau đây là một số mã sẽ yêu cầu tệp wasm từ máy chủ, biên dịch nó thành một mô-đun và sau đó chuyển mô-đun đã biên dịch cho một Web Worker:

fetch("test.wasm").then(response => 
  response.arrayBuffer()
).then(bytes =>
  WebAssembly.compile(bytes)
).then(WasmModule =>
  g_WebWorker.postMessage(WasmModule)
); 

Đoạn mã trên cũng sẽ hoạt động trong Web Worker nếu bạn muốn làm điều này ngược lại (tải và biên dịch mô-đun trong Web Worker rồi chuyển mô-đun vào chuỗi giao diện người dùng) . Điểm khác biệt duy nhất là bạn sẽ tự sử dụng .postMessage thay vì g_WebWorker.postMessge trong ví dụ trên.

Sau đây là một số mã ví dụ tạo Web Worker trong chuỗi giao diện người dùng, tải tệp wasm và sau đó chuyển mô-đun đã biên dịch cho Web Worker:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <input type="button" value="Test" onclick="OnClickTest();" />

    <script type="text/javascript">
      // Create a Web Worker (separate thread) that we'll pass the WebAssembly module to.
      var g_WebWorker = new Worker("WebWorker.js");
      g_WebWorker.onerror = function (evt) { console.log(`Error from Web Worker: ${evt.message}`); }
      g_WebWorker.onmessage = function (evt) { alert(`Message from the Web Worker:\n\n ${evt.data}`); }

      // Request the wasm file from the server and compile it...(Typically we would call
      // 'WebAssembly.instantiate' which compiles and instantiates the module. In this
      // case, however, we just want the compiled module which will be passed to the Web
      // Worker. The Web Worker will be responsible for instantiating the module.)
      fetch("test.wasm").then(response =>
        response.arrayBuffer()
      ).then(bytes =>
        WebAssembly.compile(bytes)
      ).then(WasmModule =>
        g_WebWorker.postMessage({ "MessagePurpose": "CompiledModule", "WasmModule": WasmModule })
      );

      function OnClickTest() {
        // Ask the Web Worker to add two values
        g_WebWorker.postMessage({ "MessagePurpose": "AddValues", "Val1": 1, "Val2": 2 });
      }
    </script>
  </body>
</html> 

Sau đây là nội dung của tệp WebWorker.js của chúng tôi :

 var g_importObject = {
   'env': {
     'memoryBase': 0,
     'tableBase': 0,
     'memory': new WebAssembly.Memory({ initial: 256 }),
     'table': new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
   }
 };

// The WebAssembly module instance that we'll be working with
var g_objInstance = null;

// Listen for messages from the main thread. Because all messages to this thread come through
// this method, we need a way to know what is being asked of us which is why we included the
// MessagePurpose property.
self.onmessage = function (evt) {
  // If we've been asked to call the module's Add method then...
  var objData = evt.data;
  var sMessagePurpose = objData.MessagePurpose;
  if (sMessagePurpose === "AddValues") {

    // Call the add method in the WebAssembly module and pass the result back to the main thread
    var iResult = g_objInstance.exports._add(objData.Val1, objData.Val2);
    self.postMessage(`This is the Web Worker...The result of ${objData.Val1.toString()} + ${objData.Val2.toString()} is ${iResult.toString()}.`);

  } // If we've been passed a compiled WebAssembly module then...
  else if (sMessagePurpose === "CompiledModule") {

    // NOTE: Unlike when we pass in the bytes to instantiate, we don't have a separate 'instance'
    // and 'modules' object returned in this case since we started out with the module object.
    // We're only passed back the instance in this case.
    WebAssembly.instantiate(objData.WasmModule, g_importObject).then(instance => 
      g_objInstance = instance // Hold onto the module's instance so that we can reuse it
    );

  }
} 

Sau đây là mã C cũng như dòng lệnh cần thiết để biến mã thành mô-đun WebAssembly cho bài viết hôm nay:

int add(int x, int y){ return x + y; } 

 emcc test.c -s WASM=1 -s SIDE_MODULE=1 -O1 -o test.wasm  

Nhân viên web nội tuyến

Trong khi đọc về cách sử dụng Web Worker với mô-đun WebAssembly, tôi đã xem qua một nhận xét về cách điều này thêm một yêu cầu mạng khác (một cho tệp wasm và bây giờ là một nhận xét khác cho tệp JavaScript của Web Worker) .

Cách sau có thể không phù hợp với mọi trường hợp, đặc biệt nếu mã của Web Worker lớn hoặc phức tạp, nhưng có một cách để tạo một Web Worker nội tuyến bằng cách sử dụng đối tượng Blob .

Bạn có thể chuyển cho đối tượng Blob một chuỗi JavaScript nhưng tôi thấy rằng việc sử dụng một chuỗi chữ rất khó làm việc và việc có một phần trong HTML cảm thấy tự nhiên hơn, đó là lý do tại sao chúng tôi đang sử dụng thẻ Script tùy chỉnh trong mã mẫu bên dưới .

Sau đây là một ví dụ về cách bạn có thể tạo một Web Worker nội tuyến:

 <!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <input type="button" value="Test" onclick="OnClickTest();" />

    <script id="scriptWorker" type="javascript/worker">
      var g_importObject = { 
        'env': { 
          'memoryBase': 0,
          'tableBase': 0,
          'memory': new WebAssembly.Memory({ initial: 256 }),
          'table': new WebAssembly.Table({ initial: 0, element: 'anyfunc' })
        }
      };

      // The WebAssembly module instance that we'll be working with
      var g_objInstance = null;

      // Listen for messages from the main thread. Because all messages to this thread come
      // through this method, we need a way to know what is being asked of us which is why
      // we included the MessagePurpose property.
      self.onmessage = function (evt) {
        // If we've been asked to call the module's Add method then...
        var objData = evt.data;
        var sMessagePurpose = objData.MessagePurpose;
        if (sMessagePurpose === "AddValues") {

          // Call the add method in the WebAssembly module and pass the result back to the
          // main thread
          var iResult = g_objInstance.exports._add(objData.Val1, objData.Val2);
          self.postMessage(`This is the Web Worker...The result of ${objData.Val1.toString()} + ${objData.Val2.toString()} is ${iResult.toString()}.`); 

        }// If we've been passed a compiled WebAssembly module then...
        else if (sMessagePurpose === "CompiledModule") {

          // NOTE: Unlike when we pass in the bytes to instantiate, we don't have a
          // separate 'instance' and 'modules' object returned in this case since we
          // started out with the module object. We're only passed back the instance in
          // this case.
          WebAssembly.instantiate(objData.WasmModule, g_importObject).then(instance =>
            g_objInstance = instance // Hold onto the module's instance so that we can reuse it
          );

        }
      }
    </script>

    <script type="text/javascript">
      // Load the text from our special Script tag into a Blob and then grab the URI from
      // the blob
      var bInlineWorker = new Blob([document.getElementById("scriptWorker").textContent]);
      var sBlobURL = window.URL.createObjectURL(bInlineWorker);

      // Create a Web Worker (separate thread) that we'll pass the WebAssembly module to.
      var g_WebWorker = new Worker(sBlobURL);
      g_WebWorker.onerror = function (evt) { console.log(`Error from Web Worker: ${evt.message}`); }
      g_WebWorker.onmessage = function (evt) { alert(`Message from the Web Worker:\n\n ${evt.data}`); }

      // Request the wasm file from the server and compile it...(Typically we would call
      // 'WebAssembly.instantiate' which compiles and instantiates the module. In this
      // case, however, we just want the compiled module which will be passed to the Web
      // Worker. The Web Worker will be responsible for instantiating the module.)
      fetch("test.wasm").then(response =>
        response.arrayBuffer()
      ).then(bytes =>
        WebAssembly.compile(bytes)
      ).then(WasmModule =>
        g_WebWorker.postMessage({ "MessagePurpose": "CompiledModule", "WasmModule": WasmModule })
      );

      function OnClickTest() {
        // Ask the Web Worker to add two values
        g_WebWorker.postMessage({ "MessagePurpose": "AddValues", "Val1": 1, "Val2": 2 });
      }
    </script>
  </body>
</html>

Tóm lược

Một điều thú vị về Nhân viên web là họ cũng có quyền truy cập vào IndexedDB, có nghĩa là luồng công nhân cũng có thể xử lý bộ nhớ đệm nếu bạn muốn làm việc với các mô-đun WebAssembly hoàn toàn từ luồng công nhân.

Chúng tôi không đào sâu vào bộ nhớ đệm của mô-đun WebAssembly trong bài viết này, nhưng nếu bạn quan tâm, bạn có thể xem bài viết trước của chúng tôi: WebAssembly - Bộ nhớ đệm vào HTML5 IndexedDB

Trọng tâm của bài viết hôm nay với Nhân viên web chỉ xoay quanh những gì cần thiết khi làm việc với các mô-đun WebAssembly. Nếu bạn muốn biết thêm về Nhân viên web, tôi có một số bài viết có thể quan tâm:

0 hữu ích 0 bình luận 16k xem chia sẻ

Có thể bạn quan tâm

loading