Пост Запуск Go в браузере с WASM и Web Workers впервые появился на Qvault.
Недавно мы внесли большие изменения в то, как мы запускаем Go в браузере на Qvault, и хотим объяснить улучшения. Web Workers — это причина, по которой мы смогли решить некоторые серьезные проблемы с кодированием, связанные с браузером, которые сдерживали нас. Считайте эту статью продолжением статьи Запуск Go в браузере с помощью Web Assembly.
При публикации нашего последнего курса Алгоритмы Big-O нам понадобился способ печатать вывод консоли во время выполнения кода. Мы столкнулись с проблемой при запуске в браузере ресурсоемких алгоритмов; браузер настолько увязает, что не может отображать новые строки вывода. Мы решили внедрить веб-воркеров, и они ловко решили проблему.
Проблема
В старом Qvault весь вывод консоли печатался сразу. Программа выполнялась, затем отображался вывод. Мы обнаружили, что это далеко не идеально, потому что часто бывает полезно видеть, когда что-то печатается, особенно при попытке оптимизировать алгоритм по скорости.
Например, этот код использовался для вывода всего вывода сразу:
package main
import (
"fmt"
)
func main(){
const max = 100000000
for i := 0; i < max; i++{
if i % (max/10) == 0{
fmt.Println(i)
}
}
}
После добавления веб-воркеров теперь он правильно печатает каждое число во время выполнения. Можете убедиться сами на детской площадке здесь.
Что такое веб-воркер?
Веб-воркеры — это простое средство веб-контента для запуска сценариев в фоновых потоках.
Другими словами, это наш способ, наконец, вырваться из тисков однопоточного JavaScript! Мы можем разгрузить дорогостоящие задачи в другой поток выполнения. При этом браузер может свободно отображать обновления на экране.
Как это работает — Рабочий
Как вы знаете, мы компилируем код в редакторе в WASM на наших серверах. Если вам интересна эта часть, вы можете прочитать об этом в нашем предыдущем посте. Как только код скомпилирован в Web Assembly, он отправляется обратно в наш интерфейс для выполнения.
Чтобы запустить Web Worker, нам нужен скрипт, определяющий worker. Это просто файл JavaScript:
addEventListener('message', async (e) => {
// initialize the Go WASM glue
const go = new self.Go();
// e.data contains the code from the main thread
const result = await WebAssembly.instantiate(e.data, go.importObject);
// hijack the console.log function to capture stdout
let oldLog = console.log;
// send each line of output to the main thread
console.log = (line) => { postMessage({
message: line
}); };
// run the code
await go.run(result.instance);
console.log = oldLog;
// tell the main thread we are done
postMessage({
done: true
});
}, false);
The worker communicates with the main thread by listening to message
events, and sending data back via the postMessage
function.
Примечание. Я пропустил файл wasm_exec.js, который необходим рабочему процессу для запуска кода Go, но его можно найти на вашем компьютере, если у вас установлен Go.
cat $(go env GOROOT)/misc/wasm/wasm_exec.js
Как это работает — Основная тема
Теперь, когда у нас есть рабочий файл, который может выполнять скомпилированную веб-сборку, давайте посмотрим, как основной поток взаимодействует с рабочим. Я создал модуль ES6, который экспортирует некоторые вспомогательные функции:
export function getWorker(lang) {
return {
webWorker: new window.Worker(`/${lang}_worker.js`),
lang
};
}
export function useWorker(worker, params, callback) {
const promise = new Promise((resolve, reject) => {
worker.webWorker.onmessage = (event) => {
if (event.data.done) {
resolve();
return;
}
if (event.data.error) {
reject(event.data.error);
return;
}
callback(event.data.message);
};
});
worker.webWorker.postMessage(params);
return promise;
}
export function terminateWorker(worker) {
worker.webWorker.terminate();
}
Когда страница загрузится, мы создадим новый веб-воркер, используя getWorker
. Когда пользователь запускает некоторый код, мы отправляем код рабочему процессу, используя useWorker
. Когда мы выходим из редактора кода, мы можем очистить рабочий процесс, используя terminateWorker
.
Функция useWorker
— самая интересная часть поста. Требуется рабочий процесс, созданный с помощью getWorker
, объект с именем params
, который будет передан рабочему процессу (он содержит скомпилированный WASM), и функция обратного вызова, которая будет выполняться, когда рабочий закончит работу.
Например, в нашем приложении Vue мы используем эти функции следующим образом:
this.output = [];
this.isLoading = true;
const wasm = await compileGo(this.code);
await useWorker(this.worker, wasm, (data) => {
this.output.push(data);
});
this.isLoading = false;
А поскольку this.output
является реактивным свойством нашего экземпляра Vue, каждый раз, когда мы получаем данные от Web Worker, в консоль выводится новый вывод.
Спасибо за чтение!
Подпишитесь на нас в Твиттере @q_vault, если у вас есть какие-либо вопросы или комментарии
Пройдите несколько курсов кодирования на нашей новой платформе
Подпишитесь на нашу рассылку, чтобы получать больше статей по программированию