O suporte a linhas de execução do WebAssembly foi lançado no Chrome 70 em um teste de origem.
O WebAssembly (Wasm) permite a compilação de código escrito em C++ e outras linguagens para execução na Web. Um recurso muito útil de aplicativos nativos é a capacidade de usar linhas de execução, uma primitiva para computação paralela. A maioria dos desenvolvedores de C e C++ conhece as pthreads, que é uma API padronizada para gerenciamento de linhas de execução em um aplicativo.
O WebAssembly Community Group tem trabalhado para trazer linhas de execução para a Web e permitir aplicativos multithread reais. Como parte desse esforço, o V8 implementou o suporte necessário para linhas de execução no mecanismo do WebAssembly, disponível em um teste de origem. Os testes de origem permitem que os desenvolvedores testem novos recursos da Web antes que eles sejam totalmente padronizados. Isso nos permite coletar feedback real de desenvolvedores corajosos, o que é essencial para validar e melhorar novos recursos.
A versão 70 do Chrome oferece suporte a threads para WebAssembly. Recomendamos que os desenvolvedores interessados comecem a usá-las e nos enviem feedback.
Linhas de execução? E os workers?
Os navegadores oferecem suporte ao paralelismo por meio de Web Workers desde 2012 no Chrome 4. Na verdade, é normal ouvir termos como "na linha de execução principal" etc. No entanto, os Web Workers não compartilham dados mutáveis entre si, dependendo da transmissão de mensagens para comunicação. Na verdade, o Chrome aloca um novo mecanismo V8 para cada um deles (chamados de isolados). Os isolados não compartilham o código compilado nem objetos JavaScript e, portanto, não podem compartilhar dados mutáveis, como pthreads.
Já as linhas de execução do WebAssembly podem compartilhar a mesma memória Wasm. O armazenamento subjacente da memória compartilhada é realizado com um SharedArrayBuffer, uma primitiva do JavaScript que permite compartilhar o conteúdo de um único ArrayBuffer simultaneamente entre workers. Cada linha de execução do WebAssembly é executada em um worker da Web, mas a memória compartilhada do Wasm permite que elas funcionem de maneira semelhante às plataformas nativas. Isso significa que os aplicativos que usam threads Wasm são responsáveis por gerenciar o acesso à memória compartilhada, como em qualquer aplicativo com threads tradicional. Há muitas bibliotecas de código escritas em C ou C++ que usam pthreads. Elas podem ser compiladas para Wasm e executadas no modo de linha de execução real, permitindo que mais núcleos trabalhem nos mesmos dados simultaneamente.
Um exemplo simples
Confira um exemplo de um programa "C" simples que usa linhas de execução.
#include <pthread.h>
#include <stdio.h>
// Calculate Fibonacci numbers shared function
int fibonacci(int iterations) {
int val = 1;
int last = 0;
if (iterations == 0) {
return 0;
}
for (int i = 1; i < iterations; i++) {
int seq;
seq = val + last;
last = val;
val = seq;
}
return val;
}
// Start function for the background thread
void *bg_func(void *arg) {
int *iter = (void *)arg;
*iter = fibonacci(*iter);
return arg;
}
// Foreground thread and main entry point
int main(int argc, char *argv[]) {
int fg_val = 54;
int bg_val = 42;
pthread_t bg_thread;
// Create the background thread
if (pthread_create(&bg_thread, NULL, bg_func, &bg_val)) {
perror("Thread create failed");
return 1;
}
// Calculate on the foreground thread
fg_val = fibonacci(fg_val);
// Wait for background thread to finish
if (pthread_join(bg_thread, NULL)) {
perror("Thread join failed");
return 2;
}
// Show the result from background and foreground threads
printf("Fib(42) is %d, Fib(6 * 9) is %d\n", bg_val, fg_val);
return 0;
}
Esse código começa com a função main()
, que declara duas
variáveis fg_val
e
bg_val
. Há também uma função chamada
fibonacci()
, que será chamada pelas duas
linhas de execução neste exemplo. A função main()
cria uma linha de execução em segundo plano usando pthread_create()
,
cuja tarefa é calcular o valor da sequência numérica de Fibonacci correspondente ao
valor da variável bg_val
. Enquanto isso, a
função main()
em execução na linha de execução em primeiro plano
calcula-a para a variável fg_val
. Quando a
linha de execução em segundo plano for concluída, os resultados serão impressos.
Compilar para suporte a linhas de execução
Primeiro, você precisa ter o SDK emscripten instalado, de preferência a versão 1.38.11 ou mais recente. Para criar o código de exemplo com linhas de execução ativadas para execução no navegador, precisamos transmitir algumas flags extras para o compilador emscripten emcc. Nossa linha de comando tem esta aparência:
emcc -O2 -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=2 -o test.js test.c
O argumento de linha de comando '-s USE_PTHREADS=1
' ativa o suporte de linha de execução para
o módulo WebAssembly compilado, e o argumento '-s PTHREAD_POOL_SIZE=2
'
informa ao compilador para gerar um pool de duas (2) linhas de execução.
Quando o programa é executado, ele carrega o módulo WebAssembly,
cria um worker da Web para cada uma das linhas de execução no pool de linhas de execução, compartilha o módulo
com cada um dos workers, neste caso são dois, e eles são usados sempre que uma
chamada para pthread_create()
é feita. Cada worker instancia o
módulo Wasm com a mesma memória, permitindo que eles cooperem. As mudanças mais recentes
do V8 na versão 7.0 compartilham o código nativo compilado dos módulos Wasm que são transmitidos
entre workers, o que permite que até mesmo aplicativos muito grandes sejam escalonados para muitos
workers. É importante garantir que o tamanho do pool de linhas de execução seja igual ao
número máximo de linhas de execução necessárias pelo aplicativo. Caso contrário, a criação de linhas de execução poderá falhar.
Ao mesmo tempo, se o tamanho do pool de threads for muito grande, você vai criar
Web Workers desnecessários que não farão nada além de usar memória.
Como testar
A maneira mais rápida de testar nosso módulo do WebAssembly é ativar o
suporte experimental de linhas de execução do WebAssembly no Chrome 70 e versões mais recentes. Acesse o
URL about://flags
no navegador, conforme mostrado abaixo:
Em seguida, encontre a configuração experimental de linhas de execução do WebAssembly, que tem esta aparência:
Mude a configuração para Ativado, conforme mostrado abaixo, e reinicie o navegador.
Depois que o navegador for reiniciado, tente carregar o módulo de WebAssembly com uma página HTML mínima, contendo apenas este conteúdo:
<!DOCTYPE html>
<html>
<title>Threads test</title>
<body>
<script src="test.js"></script>
</body>
</html>
Para testar esta página, você precisa executar algum tipo de servidor da Web e carregá-lo no navegador. Isso fará com que o módulo WebAssembly seja carregado e executado. A abertura do DevTools mostra a saída da execução, e você verá algo como a imagem de saída abaixo no console:
Nosso programa WebAssembly com linhas foi executado com sucesso. Recomendamos que você teste seu próprio aplicativo com várias linhas de execução usando as etapas descritas acima.
Como testar no campo com um teste de origem
Testar linhas de execução ativando flags experimentais no navegador é adequado para fins de desenvolvimento, mas, se você quiser testar seu aplicativo no campo, faça isso com o que é conhecido como teste de origem.
Com os testes de origem, você pode testar recursos experimentais com seus usuários ao receber um token de teste vinculado ao seu domínio. Em seguida, você pode implantar seu app e esperar que ele funcione em um navegador compatível com o recurso que está sendo testado (neste caso, o Chrome 70 ou mais recente). Para receber seu próprio token e executar um teste de origem, use o formulário de inscrição.
Hospedamos nosso exemplo simples acima usando um token de teste de origem para que você possa testar por conta própria sem precisar criar nada.
Se você quiser saber o que quatro linhas de execução em paralelo podem fazer para a arte ASCII, confira também esta demonstração.
Envie feedback para nós
As linhas de execução do WebAssembly são uma nova primitiva extremamente útil para portar aplicativos para a Web. Agora é possível executar aplicativos e bibliotecas C e C++ que exigem suporte a pthreads no ambiente do WebAssembly.
Estamos procurando feedback de desenvolvedores que testam esse recurso, porque ele vai ajudar a informar o processo de padronização e validar a utilidade dele. A melhor maneira de enviar feedback é informar problemas e/ou se envolver com o processo de padronização no Grupo da comunidade do WebAssembly.