Hay subprocesos de WebAssembly listos para probar en Chrome 70

La compatibilidad con subprocesos de WebAssembly se envió en Chrome 70, en una prueba de origen.

Alex Danilo

WebAssembly (Wasm) permite la compilación de código escrito en C++ y otros lenguajes para ejecutar en la Web. Una función muy útil de las aplicaciones nativas es la capacidad de usar subprocesos, una primitiva para el procesamiento en paralelo. La mayoría de los desarrolladores de C y C++ están familiarizados con pthreads, que es una API estandarizada para la administración de subprocesos en una aplicación.

El grupo de la comunidad WebAssembly trabajó para llevar subprocesos a la Web y habilitar aplicaciones reales de varios subprocesos. Como parte de este esfuerzo, V8 implementó la compatibilidad necesaria para los subprocesos en el motor WebAssembly, que está disponible en una prueba de origen. Las pruebas de origen permiten a los desarrolladores experimentar con funciones web nuevas antes de que se estandaricen por completo. Esto nos permite recopilar comentarios reales de desarrolladores intrépidos, lo cual es fundamental para validar y mejorar las nuevas funciones.

La versión de Chrome 70 admite subprocesos para WebAssembly, y alentamos a los desarrolladores interesados a comenzar a usarlos y enviarnos comentarios.

¿Hilos? ¿Qué ocurre con los trabajadores?

Los navegadores admiten el paralelismo a través de Web Workers desde 2012 en Chrome 4; de hecho, es normal escuchar términos como "en el subproceso principal", etc. Sin embargo, los trabajadores web no comparten datos mutables entre ellos, sino que dependen del paso de mensajes para la comunicación. De hecho, Chrome asigna un nuevo motor V8 para cada uno de ellos (llamados elementos aislados). Los aislamientos no comparten código compilado ni objetos de JavaScript, por lo que no pueden compartir datos mutables como pthreads.

Por otro lado, los subprocesos de WebAssembly son subprocesos que pueden compartir la misma memoria de Wasm. El almacenamiento subyacente de la memoria compartida se realiza con un SharedArrayBuffer, un tipo primitivo de JavaScript que permite compartir el contenido de un solo ArrayBuffer de forma simultánea entre los trabajadores. Cada subproceso de WebAssembly se ejecuta en un Web Worker, pero su memoria Wasm compartida le permite trabajar de manera similar a como lo hacen en plataformas nativas. Esto significa que las aplicaciones que usan subprocesos de Wasm son responsables de administrar el acceso a la memoria compartida, como en cualquier aplicación de subprocesos tradicional. Hay muchas bibliotecas de código existentes escritas en C o C++ que usan pthreads y se pueden compilar en Wasm y ejecutarse en modo de subproceso verdadero, lo que permite que más núcleos funcionen en los mismos datos de forma simultánea.

Un ejemplo sencillo

Este es un ejemplo de un programa "C" simple que usa subprocesos.

#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;
}

Ese código comienza con la función main(), que declara 2 variables fg_val y bg_val. También hay una función llamada fibonacci(), a la que ambos subprocesos llamarán en este ejemplo. La función main() crea un subproceso en segundo plano con pthread_create() cuya tarea es calcular el valor de secuencia del número de fibonacci correspondiente al valor de la variable bg_val. Mientras tanto, la función main() que se ejecuta en el subproceso en primer plano lo calcula para la variable fg_val. Una vez que termine de ejecutarse el subproceso en segundo plano, se mostrarán los resultados.

Compila para admitir subprocesos

Primero, debes tener instalado el SDK emscripten, preferentemente la versión 1.38.11 o una posterior. Para compilar nuestro código de ejemplo con subprocesos habilitados para ejecutarse en el navegador, debemos pasar algunas marcas adicionales al compilador emscripten emcc. Nuestra línea de comandos se ve de la siguiente manera:

emcc -O2 -s USE_PTHREADS=1 -s PTHREAD_POOL_SIZE=2 -o test.js test.c

El argumento de línea de comandos "-s USE_PTHREADS=1" activa la compatibilidad con subprocesos para el módulo WebAssembly compilado y el argumento "-s PTHREAD_POOL_SIZE=2" le indica al compilador que genere un grupo de dos (2) subprocesos.

Cuando se ejecute el programa, de forma interna cargará el módulo WebAssembly, creará un Web Worker para cada uno de los subprocesos en el conjunto de subprocesos, compartirá el módulo con cada uno de los trabajadores (en este caso, será 2), y se usarán cada vez que se realice una llamada a pthread_create(). Cada trabajador crea una instancia del módulo Wasm con la misma memoria, lo que le permite cooperar. Los cambios más recientes de V8 en la versión 7.0 comparten el código nativo compilado de los módulos de Wasm que se pasan entre trabajadores, lo que permite que incluso las aplicaciones muy grandes escalen a muchos trabajadores. Ten en cuenta que, de lo contrario, es conveniente que te asegures de que el tamaño del conjunto de subprocesos sea igual a la cantidad máxima de subprocesos que tu aplicación necesita. De lo contrario, la creación de los subprocesos podría fallar. Al mismo tiempo, si el tamaño del conjunto de subprocesos es demasiado grande, crearás trabajadores web innecesarios que solo usarán memoria.

Cómo probarlo

La forma más rápida de probar nuestro módulo de WebAssembly es activar la compatibilidad experimental con subprocesos de WebAssembly a partir de Chrome 70. Navega a la URL about://flags en tu navegador como se muestra a continuación:

Página de funciones experimentales de Chrome

A continuación, busca la configuración experimental de subprocesos de WebAssembly que se ve de la siguiente manera:

Configuración de subprocesos de WebAssembly

Cambia la configuración a Habilitado como se muestra a continuación y, luego, reinicia el navegador.

Se habilitó la configuración de subprocesos de WebAssembly

Después de reiniciar el navegador, podemos intentar cargar el módulo de WebAssembly con subprocesos con una página HTML mínima, que incluya solo el siguiente contenido:

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

Para probar esta página, tendrás que ejecutar algún tipo de servidor web y cargarlo desde tu navegador. Eso hará que el módulo WebAssembly se cargue y se ejecute. Cuando abres Herramientas para desarrolladores, se muestra el resultado de la ejecución. Deberías ver una imagen similar a la que se muestra a continuación en la consola:

Resultado de la consola del programa Fibonacci

Nuestro programa WebAssembly con subprocesos se ejecutó correctamente. Te recomendamos que pruebes tu propia aplicación en subprocesos mediante los pasos que se describieron más arriba.

Pruebas en el campo con una prueba de origen

Probar los subprocesos mediante la activación de marcas experimentales en el navegador está bien para fines de desarrollo, pero si deseas probar tu aplicación en el campo, puedes hacerlo con lo que se conoce como prueba de origen.

Las pruebas de origen te permiten probar funciones experimentales con tus usuarios mediante la obtención de un token de prueba vinculado al dominio. Luego, podrás implementar tu app y esperar que funcione en un navegador compatible con la función que estás probando (en este caso, Chrome 70 y versiones posteriores). Si quieres obtener tu propio token para ejecutar una prueba de origen, usa el formulario de la aplicación.

Alojamos nuestro ejemplo simple anterior con un token de prueba de origen para que puedas probarlo por tu cuenta sin necesidad de compilar nada.

Si quieres ver lo que 4 subprocesos que se ejecutan en paralelo pueden hacer para el arte ASCII, también debes consultar esta demostración.

Envíanos tus comentarios.

Los subprocesos de WebAssembly son una nueva primitiva extremadamente útil para migrar aplicaciones a la Web. Ahora es posible ejecutar aplicaciones y bibliotecas de C y C++ que requieren compatibilidad con pthreads en el entorno de WebAssembly.

Buscamos comentarios de los desarrolladores que prueban esta función, ya que nos ayudarán a informar el proceso de estandarización y a validar su utilidad. La mejor manera de enviar comentarios es informar problemas o participar en el proceso de estandarización del grupo de la comunidad de WebAssembly.