WebAssembly Threads pronto para teste no Chrome 70

O suporte para thread do WebAssembly foi lançado no Chrome 70 em um teste de origem.

Alex Danilo

O WebAssembly (Wasm) permite a compilação de códigos escritos em C++ e outras linguagens para execução na Web. Um recurso muito útil dos aplicativos nativos é a capacidade de usar linhas de execução, que são um primitivo para computação paralela. A maioria dos desenvolvedores em C e C++ conhece o pthreads, que é uma API padronizada para gerenciamento de linhas de execução em um aplicativo.

O grupo da comunidade WebAssembly está trabalhando para disponibilizar as conversas na Web a fim de permitir aplicativos reais com várias linhas de execução. Como parte desse esforço, o V8 implementou o suporte necessário para linhas de execução no mecanismo WebAssembly, disponível em um teste de origem. Os testes de origem permitem que os desenvolvedores testem novos recursos da Web antes de serem totalmente padronizados. Isso nos permite coletar feedback do mundo real de desenvolvedores intrépidos, o que é fundamental para validar e melhorar novos recursos.

A versão do Chrome 70 é compatível com linhas de execução do WebAssembly. Incentivamos os desenvolvedores interessados a começar a usá-las e enviar feedback.

Conversas? E quanto aos workers?

Os navegadores oferecem suporte ao paralelismo por 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 eles, em vez de depender da transmissão de mensagens para comunicação. Na verdade, o Chrome aloca um novo mecanismo V8 para cada um deles (chamado de isolamento). Os isolamentos não compartilham código compilado nem objetos JavaScript e, portanto, não podem compartilhar dados mutáveis, como pthreads.

As linhas de execução do WebAssembly, por outro lado, podem compartilhar a mesma memória do Wasm. O armazenamento da memória compartilhada é alcançado com um SharedArrayBuffer, um primitivo de JavaScript que permite compartilhar o conteúdo de um único ArrayBuffer simultaneamente entre workers. Cada linha de execução do WebAssembly é executada em um Web Worker, mas a memória Wasm compartilhada permite que elas funcionem de maneira semelhante às plataformas nativas. Isso significa que os aplicativos que usam linhas de execução Wasm são responsáveis por gerenciar o acesso à memória compartilhada como em qualquer aplicativo com linha de execução tradicional. Existem muitas bibliotecas de código escritas em C ou C++ que usam pthreads. Elas podem ser compiladas no Wasm e executadas no modo de linha de execução verdadeira, permitindo que mais núcleos trabalhem nos mesmos dados simultaneamente.

Um exemplo simples

Confira um exemplo de um programa em 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 do número Fibonacci correspondente ao valor da variável bg_val. Enquanto isso, a função main() em execução na linha de execução de primeiro plano a calcula para a variável fg_val. Quando a linha de execução em segundo plano termina a execução, os resultados são impressos.

Compilar para oferecer suporte a linhas de execução

Primeiro, você precisa ter o SDK emscripten instalado, de preferência na versão 1.38.11 ou mais recente. Para criar nosso 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 (links em inglês). Nossa linha de comando é semelhante a esta:

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 a linhas 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 Web Worker para cada uma das linhas de execução no pool e compartilha o módulo com cada worker. Neste caso, ele é 2, que vai ser usado sempre que uma chamada para pthread_create() for feita. Cada worker instancia o módulo Wasm com a mesma memória, permitindo que eles colaborem. As mais recentes mudanças 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. Observe que faz sentido garantir que o tamanho do pool de linhas de execução seja igual ao número máximo de linhas de execução de que seu aplicativo precisa. Caso contrário, a criação de linhas de execução pode falhar. Ao mesmo tempo, se o tamanho do pool de linhas de execução for muito grande, você criará Web Workers desnecessários que ficarão sem fazer nada além de usar a memória.

Como testar

A maneira mais rápida de testar nosso módulo WebAssembly é ativar o suporte experimental às linhas de execução do WebAssembly no Chrome 70 em diante. Navegue até o URL about://flags no navegador, conforme mostrado abaixo:

Página de sinalizações do Chrome

Em seguida, encontre a configuração experimental de linhas de execução do WebAssembly, que tem esta aparência:

Configuração de linhas de execução do WebAssembly

Mude a configuração para Ativado, conforme mostrado abaixo, e reinicie o navegador.

Configuração de linhas de execução do WebAssembly ativada

Depois que o navegador for reiniciado, tente carregar o módulo WebAssembly em linha de execução com uma página HTML mínima, contendo apenas o seguinte conteúdo:

<!DOCTYPE html>
<html>
  <title>Threads test</title>
  <body>
    <script src="test.js"></script>
  </body>
</html>

Para testar esta página, é necessário executar alguma forma de servidor da Web e carregá-la do navegador. Isso fará com que o módulo WebAssembly seja carregado e executado. Quando você abre o DevTools, a saída da execução é mostrada, e você vê algo parecido com a imagem de saída abaixo no console:

Saída do console do programa fibonacci

Nosso programa WebAssembly com linhas de execução foi executado. Recomendamos que você teste seu próprio aplicativo em linha de execução usando as etapas descritas acima.

Como testar em campo com um teste de origem

Para fins de desenvolvimento, testar linhas de execução ativando flags experimentais no navegador é bom para fins de desenvolvimento. No entanto, se você quiser testar seu aplicativo em 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 usando um token de teste vinculado ao seu domínio. Em seguida, você pode implantar o app e esperar que ele funcione em um navegador com suporte ao recurso que está sendo testado (neste caso, no Chrome 70 em diante). 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 testá-lo por conta própria sem precisar criar nada.

Se você quiser ver o que quatro linhas de execução em paralelo podem fazer na arte ASCII, confira esta demonstração também.

Enviar feedback

As linhas de execução do WebAssembly são um novo primitivo extremamente útil para a portabilidade de aplicativos para a Web. Agora é possível executar aplicativos e bibliotecas em C e C++ que exigem suporte a pthreads no ambiente WebAssembly.

Queremos receber feedback dos desenvolvedores que estão testando esse recurso, porque ele vai nos 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 WebAssembly.