Threads WebAssembly prêts à être essayés dans Chrome 70

La compatibilité avec les threads WebAssembly est disponible dans Chrome 70 dans le cadre d'une phase d'évaluation.

Alex Danilo

WebAssembly (Wasm) permet de compiler du code écrit en C++ et d'autres langages à exécuter sur le Web. Une fonctionnalité très utile des applications natives est la possibilité d'utiliser des threads, une primitive pour le calcul parallèle. La plupart des développeurs C et C++ connaissent l'API pthreads, qui est une API standardisée pour la gestion des threads dans une application.

Le groupe de la communauté WebAssembly a commencé à transférer des threads sur le Web pour permettre de véritables applications multithread. Dans le cadre de cette initiative, V8 a implémenté la compatibilité nécessaire pour les threads dans le moteur WebAssembly, disponible via une phase d'évaluation. Les phases d'évaluation permettent aux développeurs de tester de nouvelles fonctionnalités Web avant qu'elles ne soient entièrement standardisées. Cela nous permet de recueillir les commentaires concrets de développeurs intrépides, ce qui est essentiel pour valider et améliorer les nouvelles fonctionnalités.

La version Chrome 70 est compatible avec les threads pour WebAssembly. Nous encourageons les développeurs intéressés à commencer à les utiliser et à nous faire part de leurs commentaires.

Des fils de discussion ? Qu'en est-il des workers ?

Les navigateurs sont compatibles avec le parallélisme via les Web Workers depuis 2012 dans Chrome 4. En fait, il est normal d'entendre des termes tels que "dans le thread principal", etc. Toutefois, les Web workers ne partagent pas de données modifiables entre eux, au lieu de s'appuyer sur la transmission de messages pour la communication. Chrome alloue d'ailleurs un nouveau moteur V8 à chacun d'eux (appelé "isolation"). Les fonctions isolées ne partagent ni code compilé, ni objets JavaScript. Par conséquent, elles ne peuvent pas partager de données modifiables telles que les pthreads.

En revanche, les threads WebAssembly sont des threads qui peuvent partager la même mémoire Wasm. Le stockage sous-jacent de la mémoire partagée est réalisé à l'aide de SharedArrayBuffer, une primitive JavaScript qui permet de partager le contenu d'un seul ArrayBuffer simultanément entre les nœuds de calcul. Chaque thread WebAssembly s'exécute dans un nœud de calcul Web, mais sa mémoire Wasm partagée lui permet de fonctionner de la même manière que sur des plates-formes natives. Cela signifie que les applications qui utilisent des threads Wasm sont responsables de la gestion de l'accès à la mémoire partagée, comme dans toute application avec fils de discussion traditionnels. De nombreuses bibliothèques de code écrites en C ou C++ utilisent des pthreads. Elles peuvent être compilées dans Wasm et exécutées en mode véritable thread, ce qui permet à davantage de cœurs de travailler simultanément sur les mêmes données.

Exemple simple

Voici un exemple de programme "C" simple qui utilise des threads.

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

Ce code commence par la fonction main(), qui déclare deux variables fg_val et bg_val. Il existe également une fonction appelée fibonacci(), qui sera appelée par les deux threads dans cet exemple. La fonction main() crée un thread d'arrière-plan à l'aide de pthread_create(), dont la tâche consiste à calculer la valeur séquentielle des nombres de fibonacci correspondant à la valeur de la variable bg_val. Pendant ce temps, la fonction main() exécutée dans le thread de premier plan le calcule pour la variable fg_val. Une fois l'exécution du thread d'arrière-plan terminée, les résultats sont affichés.

Compiler pour la compatibilité avec les threads

Tout d'abord, le SDK emscripten doit être installé, de préférence en version 1.38.11 ou ultérieure. Pour compiler notre exemple de code avec des threads activés pour s'exécuter dans le navigateur, nous devons transmettre quelques indicateurs supplémentaires au compilateur emscripten emcc. Notre ligne de commande ressemble à ceci:

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

L'argument de ligne de commande "-s USE_PTHREADS=1" active la prise en charge des threads pour le module WebAssembly compilé, et l'argument "-s PTHREAD_POOL_SIZE=2" indique au compilateur de générer un pool de deux (2) threads.

Lorsque le programme est exécuté, il charge en arrière-plan le module WebAssembly, crée un worker Web pour chacun des threads du pool de threads et partage le module avec chacun des nœuds de calcul (dans ce cas, il s'agit de 2). Ceux-ci sont utilisés chaque fois qu'un appel à pthread_create() est effectué. Chaque nœud de calcul instancie le module Wasm avec la même mémoire, ce qui leur permet de coopérer. Les dernières modifications de V8 dans la version 7.0 partagent le code natif compilé des modules Wasm transmis entre les nœuds de calcul, ce qui permet même aux applications très volumineuses de s'adapter à de nombreux nœuds de calcul. Notez qu'il est judicieux de s'assurer que la taille du pool de threads est égale au nombre maximal de threads dont votre application a besoin, sans quoi la création d'un thread risque d'échouer. Dans le même temps, si la taille du pool de threads est trop importante, vous allez créer des Web Workers inutiles qui ne feront rien d'autre qu'utiliser la mémoire.

Comment l'essayer ?

Le moyen le plus rapide de tester notre module WebAssembly consiste à activer la prise en charge expérimentale des threads WebAssembly dans Chrome 70 et versions ultérieures. Accédez à l'URL about://flags dans votre navigateur, comme indiqué ci-dessous:

Page des indicateurs Chrome

Recherchez ensuite le paramètre expérimental de threads WebAssembly, qui se présente comme suit:

Paramètre des threads WebAssembly

Définissez le paramètre sur Activé comme indiqué ci-dessous, puis redémarrez votre navigateur.

Paramètre des threads WebAssembly activé

Une fois le navigateur redémarré, nous pouvons essayer de charger le module WebAssembly à fils de discussion avec une page HTML minimale, ne contenant que le contenu suivant:

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

Pour consulter cette page, vous devez exécuter un serveur Web et le charger depuis votre navigateur. Cela entraînera le chargement et l'exécution du module WebAssembly. Lorsque vous ouvrez DevTools, le résultat de l'exécution s'affiche. Vous devriez voir une sortie semblable à l'image ci-dessous dans la console:

Sortie de la console par le programme Fibonacci

Notre programme WebAssembly avec des threads a bien été exécuté. Nous vous encourageons à essayer votre propre application avec fils de discussion en suivant les étapes décrites ci-dessus.

Effectuer des tests sur le terrain avec une phase d'évaluation

Vous pouvez tester des threads en activant des indicateurs expérimentaux dans le navigateur à des fins de développement, mais si vous souhaitez tester votre application sur le terrain, vous pouvez le faire avec ce que l'on appelle les phases d'évaluation.

Les phases d'évaluation vous permettent de tester des fonctionnalités expérimentales avec vos utilisateurs en obtenant un jeton de test lié à votre domaine. Vous pouvez ensuite déployer votre application et vous attendre à ce qu'elle fonctionne dans un navigateur compatible avec la fonctionnalité que vous testez (dans ce cas, Chrome 70 et versions ultérieures). Pour obtenir votre propre jeton permettant d'exécuter une phase d'évaluation, remplissez ce formulaire d'application.

Nous avons hébergé notre exemple simple ci-dessus en utilisant un jeton d'évaluation. Vous pouvez donc l'essayer par vous-même sans avoir à créer quoi que ce soit.

Si vous souhaitez voir ce que quatre threads exécutés en parallèle peuvent faire pour l'art ASCII, vous devez également regarder cette démonstration.

Donnez-nous votre avis

Les threads WebAssembly constituent une nouvelle primitive extrêmement utile pour le portage d'applications sur le Web. Il est maintenant possible d'exécuter des applications et des bibliothèques C et C++ qui nécessitent la compatibilité avec les pthreads dans l'environnement WebAssembly.

Nous attendons les commentaires des développeurs qui testent cette fonctionnalité, car elle nous aidera à éclairer le processus de normalisation et à valider son utilité. Le meilleur moyen d'envoyer des commentaires est de signaler des problèmes et/ou de vous impliquer dans le processus de normalisation du groupe de la communauté WebAssembly.