Javascript Работники - почему сообщение работника так долго обрабатывается, и могу ли я что-то сделать против него? - PullRequest
1 голос
/ 16 января 2020

У меня есть работник, который разделяет SharedArrayBuffer с «основным потоком». Для корректной работы я должен убедиться, что у работника есть доступ к SAB, прежде чем основной поток получит к нему доступ. (РЕДАКТИРОВАТЬ: код, создающий работника, должен быть в отдельной функции (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
};

Рабочий код всегда выполняется после test(), консоль всегда показывает main, затем 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. Но если это применимо, сообщение будет отправлено / обработано до того, как будут выполнены обратные вызовы тайм-аута.
  • К go даже дальше: Если я попытаюсь заняться ожиданием после postMessage путем чтения из SAB, пока оно не изменит одно значение заблокирует программу бесконечно . Для меня это означает , что Браузер не отправляет сообщение, пока стек вызовов не станет пустым Насколько я знаю, это поведение не задокументировано, и я не могу это объяснить.

Подводя итог: Я хочу знать, как браузер определяет, когда отправлять сообщение и обрабатывать его работником, если вызов postMessage находится внутри функции. Я уже нашел обходной путь (глобальные переменные), поэтому меня больше интересует, как это работает за кулисами. Но если кто-то может показать мне рабочий пример, я возьму его.

РЕДАКТИРОВАТЬ 2:

Код, использующий глобальную переменную (код, который работает нормально), выглядит следующим образом

function init() {
//Unchanged
}

var array = init(); //global

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

Он печатает starting, затем main на консоли.

Что также стоит отметить: Если я отлаживаю код с помощью Firefox Browser ( Chrome не проверено) Я получаю желаемый результат без глобальной переменной (starting до main) Может кто-нибудь объяснить?

Ответы [ 2 ]

2 голосов
/ 20 января 2020

, почему рабочий не запускается сразу после того, как сообщение было отправлено [t] (= получено?). AFAIK, у рабочих есть своя собственная очередь событий, поэтому они не должны полагаться на то, что основной стек становится пустым?

Во-первых, даже если ваш рабочий объект доступен в основном потоке синхронно, на самом деле рабочий поток нужно многое сделать , прежде чем он сможет обработать ваше сообщение:

  • он должен выполнить сетевой запрос для получения сценария содержание. Даже с blobURI это асинхронная операция c.
  • она должна инициализировать весь контекст js, поэтому, даже если сетевой запрос будет быстрым, это увеличит время параллельного выполнения.
  • необходимо обработать кадр l oop после выполнения основного скрипта, чтобы обработать ваше сообщение. Даже если инициализация была молниеносной, она все равно будет ждать некоторое время.

Таким образом, в нормальных условиях очень мало шансов, что ваш работник сможет выполнить ваш код в тот момент, когда вам требуются данные.

Теперь вы говорили о блокировке основного потока.

Если я попытаюсь заняться ожиданием после postMessage, читая из SAB, пока оно не изменится, одно значение будет бесконечно блокировать программу

Во время инициализации вашего Worker сообщение временно сохраняется в главном потоке, который называется внешний порт . Только после извлечения скрипта этот внешний порт запутывается с внутренним портом , и сообщения фактически передаются в этот параллельный поток.
Таким образом, если вы заблокируете основной поток до того, как порты будут запутаны , он не сможет передать его потоку работника.

Is есть спецификация, в которой подробно описывается, когда рабочий начинает работать после отправки сообщения?

Sure , а более конкретно, очередь сообщений порта включена на шаге 26 , и Событие l oop фактически запускается на шаге 29 .

Есть ли способ убедиться, что работник начал раньше? снова получить доступ к SAB без использования глобальных переменных? [...]

Конечно, заставьте вашего работника публиковать сообщения в основной ветке, когда это было.

// 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);
0 голосов
/ 16 января 2020

Согласно MDN:

Данные, передаваемые между главной страницей и работниками, копируются, а не передаются. Объекты сериализуются по мере их передачи работнику, а затем десериализуются на другом конце. Страница и работник не совместно используют один и тот же экземпляр, поэтому конечным результатом является создание дубликата на каждом конце. Большинство браузеров реализуют эту функцию как структурированное клонирование.

Подробнее о передаче данных рабочим и от них

Вот базовый c код, который разделяет буфер с работником. Он создает массив с четными значениями (i*2) и отправляет его работнику. Он использует Atomi c операции для изменения значений буфера.

Чтобы убедиться, что рабочий запущен, вы можете просто использовать разные сообщения.

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>
...