WebAssembly Threads pronto para teste no Chrome 70

O suporte a linhas de execução do WebAssembly foi lançado no Chrome 70 em uma avaliação de origem.

Alex Danilo

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++ está familiarizada com pthreads, que é uma API padronizada para o gerenciamento de linhas de execução em um aplicativo.

O grupo da comunidade WebAssembly (em inglês) está trabalhando para levar linhas de execução à Web para 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 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.

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

As linhas de execução do WebAssembly, por outro lado, 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 os 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 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(), que tem a tarefa de calcular o valor da sequência do número 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 faz o cálculo 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 threads ativadas para execução no navegador, precisamos transmitir algumas flags extras para o compilador emscripten emcc. Nossa linha de comando fica assim:

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 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 os workers, permitindo 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 linhas de execução for muito grande, você criará web workers desnecessários e ficarão esperando apenas o uso da 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:

Página de flags 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

Altere 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 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:

Saída do console do programa da Fibonacci

Nosso programa WebAssembly com linhas foi executado com sucesso. Teste seu próprio aplicativo em linha 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 eles, é possível testar recursos experimentais com usuários por meio de um token de teste vinculado ao seu domínio. Depois disso, é possível implantar o app e esperar que ele funcione em um navegador compatível com o recurso que você está testando (neste caso, Chrome 70 em diante). Para ter seu próprio token e executar um teste de origem, use este formulário do aplicativo.

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.