1

我有一个与“主线程”共享一个 SharedArrayBuffer 的 Worker。为了正常工作,我必须确保在主线程访问 SAB 之前,worker 可以访问它。(编辑:创建工人的代码必须在一个单独的函数中(EDIT2:它返回一个指向 SAB 的数组)。)(也许,这已经不可能了,你会告诉我的)。

初始代码如下所示:

function init() {
  var code = `onmessage = function(event) {
      console.log('starting');
      var buffer=event.data;
      var arr = new Uint32Array(buffer);// I need to have this done before accessing the buffer again from the main
      //some other code, manipulating the array
  }`
  var buffer = new SharedArrayBuffer(BUFFER_ELEMENT_SIZE);
  var blob = new Blob([code], { "type": 'application/javascript' });
  var url = window.URL || window.webkitURL;
  var blobUrl = url.createObjectURL(blob);
  var counter = new Worker(blobUrl);
  counter.postMessage(buffer);
  let res = new Uint32Array(buffer);
  return res;
}

function test (){
  let array = init();
  console.log('main');
  //accessing the SAB again
};

worker 代码总是在 after 之后执行test(),控制台总是显示mainthen starting

使用超时没有帮助。考虑以下代码test

function test (){
  let array = [];
  console.log('main'); 
  setTimeout(function(){
    array = initSAB();
  },0);
  setTimeout(function(){
    console.log('main');
   //accessing the SAB again
  },0);
  console.log('end');
};

控制台end首先显示,然后是main,然后是starting

但是,即使没有超时,将缓冲区分配给 test() 函数之外的全局数组也可以完成这项工作。

我的问题如下:

  • 为什么发送消息后工作人员不直接启动(=收到?)。AFAIK,工人有自己的事件队列,所以他们不应该依赖主堆栈变空?
  • 是否有详细说明工人在发送消息后何时开始工作的规范?
  • 有没有办法在不使用全局变量的情况下再次访问 SAB 之前确保工作人员已经启动?(可以使用忙等待,但我要小心......)可能没有办法,但我想确定一下。

编辑

更准确地说:

  • 在完全并行运行的场景中,Worker 将能够在消息发布后立即对其进行处理。显然情况并非如此。
  • 大多数浏览器 API(而 Worker 就是这样一个 API)使用回调队列来处理对 API 的调用。但是如果应用了这个,消息将在执行超时回调之前发布/处理。
  • 更进一步:如果我尝试在 postMessage 之后通过从 SAB 读取直到它更改一个值来忙等待,则会无限地阻塞程序。对我来说,这意味着浏览器 在调用堆栈为空之前不会 发布消息据我所知,这种行为没有记录在案,我无法解释。

总结一下:如果 postMessage 的调用在函数内部,我想知道浏览器如何确定何时发布消息并由工作人员处理。我已经找到了一种解决方法(全局变量),所以我对它在幕后的工作方式更感兴趣。但是,如果有人可以向我展示一个可行的示例,我会接受它。

编辑2:

使用全局变量的代码(运行良好的代码)如下所示

function init() {
//Unchanged
}

var array = init(); //global

function test (){
  console.log('main');
  //accessing the SAB again
};

它打印starting,然后打印main到控制台。

还值得注意的是:如果我使用 Firefox 浏览器(Chrome 未测试)调试代码,我会在没有全局变量(之前)的情况下得到我想要的结果有人可以解释吗?startingmain

4

2 回答 2

2

为什么工人在消息被 sen[t] (= 收到?)后没有直接启动。AFAIK,工人有自己的事件队列,所以他们不应该依赖主堆栈变空?

首先,即使您的 Worker 对象在主线程中同步可用,但在实际的工作线程中,在能够处理您的消息之前还有很多事情要做:

  • 它必须执行网络请求来检索脚本内容。即使使用 blobURI,它也是一个异步操作。
  • 它必须初始化整个 js 上下文,所以即使网络请求快如闪电,这也会增加并行执行时间。
  • 它必须等待主脚本执行后的事件循环帧来处理您的消息。即使初始化是闪电般的快,它无论如何都会等待一段时间。

所以在正常情况下,你的 Worker 在你需要数据的时候执行你的代码的可能性很小。

现在你谈到了阻塞主线程。

如果我尝试通过从 SAB 读取 postMessage 后忙于等待,直到它更改一个值将无限阻塞程序

您的 Worker 初始化期间,消息暂时保存在主线程中,即所谓的外部端口中。只有在脚本的获取完成之后,这个外部端口才与内部端口纠缠在一起,并且消息实际上传递到那个并行线程。
因此,如果您在端口被纠缠之前阻塞主线程,它将无法将其传递给工作线程。

是否有详细说明工人在发送消息后何时开始工作的规范?

当然,更具体地说,端口消息队列在步骤26启用,事件循环实际上在步骤29开始。

有没有办法在不使用全局变量的情况下再次访问 SAB 之前确保工作人员已经启动?[...]

当然,让您的 Worker 向主线程发布消息。

// some precautions because all browsers still haven't reenabled SharedArrayBuffers
const has_shared_array_buffer = window.SharedArrayBuffer;

function init() {
  // since our worker will do only a single operation
  // we can Promisify it
  // if we were to use it for more than a single task, 
  // we could promisify each task by using a MessagePort
  return new Promise((resolve, reject) => {
    const code = `
    onmessage = function(event) {
      console.log('hi');
      var buffer= event.data;
      var arr = new Uint32Array(buffer);
      arr.fill(255);
      if(self.SharedArrayBuffer) {
        postMessage("done");
      }
      else {
        postMessage(buffer, [buffer]);
      }
    }`
    let buffer = has_shared_array_buffer ? new SharedArrayBuffer(16) : new ArrayBuffer(16);
    const blob = new Blob([code], { "type": 'application/javascript' });
    const blobUrl = URL.createObjectURL(blob);
    const counter = new Worker(blobUrl);
    counter.onmessage = e => {
      if(!has_shared_array_buffer) {
        buffer = e.data;
      }
      const res = new Uint32Array(buffer);
      resolve(res);
    };
    counter.onerror = reject;
    if(has_shared_array_buffer) {
      counter.postMessage(buffer);
    }
    else {
      counter.postMessage(buffer, [buffer]);
    }
  });
};

async function test (){
  let array = await init();
  //accessing the SAB again
  console.log(array);
};
test().catch(console.error);

于 2020-01-20T12:32:41.687 回答
0

根据 MDN:

在主页和工作人员之间传递的数据是复制的,而不是共享的。对象在交给工作人员时会被序列化,然后在另一端反序列化。页面和工作人员不共享同一个实例,因此最终结果是在每一端都创建了一个副本。大多数浏览器将此功能实现为结构化克隆。

阅读有关在工作人员之间传输数据的更多信息

这是一个与工作人员共享缓冲区的基本代码。它创建一个具有偶数值的数组(i*2)并将其发送给工作人员。它使用原子操作来更改缓冲区值。

为确保工作人员已启动,您可以使用不同的消息。

var code = document.querySelector('[type="javascript/worker"]').textContent;

var blob = new Blob([code], { "type": 'application/javascript' });
var blobUrl = URL.createObjectURL(blob);
var counter = new Worker(blobUrl);

var sab;

var initBuffer = function (msg) {
  sab = new SharedArrayBuffer(16);
  counter.postMessage({
    init: true, 
    msg: msg, 
    buffer: sab
  });
};

var editArray = function () {
  var res = new Int32Array(sab);
  for (let i = 0; i < 4; i++) {
    Atomics.store(res, i, i*2);
  }
  console.log('Array edited', res);
};

initBuffer('Init buffer and start worker');

counter.onmessage = function(event) {
  console.log(event.data.msg);
  if (event.data.edit) {
    editArray();
    // share new buffer with worker
    counter.postMessage({buffer: sab});
    // end worker
    counter.postMessage({end: true});
  }
};
<script type="javascript/worker">
  var sab;
  self['onmessage'] = function(event) {
    if (event.data.init) {
      postMessage({msg: event.data.msg, edit: true});
    }
    if (event.data.buffer) {
      sab = event.data.buffer;
      var sharedArray = new Int32Array(sab);
      postMessage({msg: 'Shared Array: '+sharedArray});
    }
    if (event.data.end) {
      postMessage({msg: 'Time to rest'});
    }
  };
</script>

于 2020-01-16T13:53:51.677 回答