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

La prise en charge des 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 dans d'autres langages pour l'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 les pthreads, qui sont une API standardisée pour la gestion des threads dans une application.

Le groupe de la communauté WebAssembly s'est efforcé d'intégrer des threads sur le Web pour permettre de véritables applications multithreads. Dans le cadre de cet effort, V8 a implémenté la prise en charge nécessaire des threads dans le moteur WebAssembly, disponible via un essai Origin. Les essais Origin permettent aux développeurs de tester de nouvelles fonctionnalités Web avant qu'elles ne soient entièrement normalisées. Cela nous permet de recueillir des commentaires concrets de développeurs intrépides, ce qui est essentiel pour valider et améliorer les nouvelles fonctionnalités.

La version de 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.

Fils de discussion ? Qu'en est-il des nœuds de calcul ?

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 "sur le thread principal", etc. Cependant, les Web Workers ne partagent pas de données modifiables entre eux, mais s'appuient sur la transmission de messages pour la communication. En fait, Chrome alloue un nouveau moteur V8 pour chacun d'eux (appelés "isolates"). Les isolateurs ne partagent ni code compilé ni objets JavaScript. Ils ne peuvent donc pas partager de données modifiables comme les pthreads.

En revanche, les threads WebAssembly peuvent partager la même mémoire Wasm. Le stockage sous-jacent de la mémoire partagée est effectué avec un SharedArrayBuffer, une primitive JavaScript qui permet de partager simultanément le contenu d'un seul ArrayBuffer entre les nœuds de calcul. Chaque thread WebAssembly s'exécute dans un nœud de travail Web, mais leur mémoire Wasm partagée leur permet de fonctionner comme sur les plates-formes natives. Cela signifie que les applications qui utilisent des threads Wasm sont chargées de gérer l'accès à la mémoire partagée, comme dans toute application multithread classique. De nombreuses bibliothèques de code écrites en C ou C++ qui utilisent des pthreads existent. Elles peuvent être compilées en Wasm et exécutées en mode 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 du nombre 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 la calcule pour la variable fg_val. Une fois l'exécution du thread en arrière-plan terminée, les résultats sont imprimés.

Compiler pour la compatibilité avec les threads

Tout d'abord, vous devez installer le SDK emscripten, de préférence la version 1.38.11 ou ultérieure. Pour compiler notre exemple de code avec des threads activés pour l'exécution dans le navigateur, nous devons transmettre quelques options supplémentaires au compilateur emcc emscripten. Notre ligne de commande se présente comme suit :

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 le module WebAssembly, crée un worker Web pour chacun des threads du pool de threads, partage le module avec chacun des workers (dans ce cas, il s'agit de deux) et 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 très grandes applications de s'adapter à de nombreux nœuds de calcul. Notez qu'il est judicieux de vous assurer que la taille du pool de threads est égale au nombre maximal de threads dont votre application a besoin, sinon la création de threads risque d'échouer. En même temps, si la taille du pool de threads est trop importante, vous créez des Web workers inutiles qui ne font rien d'autre qu'utiliser la mémoire.

Comment tester cette fonctionnalité ?

Le moyen le plus rapide de tester notre module WebAssembly consiste à activer la compatibilité expérimentale avec les threads WebAssembly à partir de Chrome 70. Accédez à l'URL about://flags dans votre navigateur, comme indiqué ci-dessous:

Page des indicateurs Chrome

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

Paramètre des threads WebAssembly

Définissez le paramètre sur Enabled (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 organisé en fils de discussion avec une page HTML minimale ne contenant que ce contenu:

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

Pour essayer cette page, vous devez exécuter un type de serveur Web et le charger depuis votre navigateur. Le module WebAssembly se charge et s'exécute. L'ouverture de DevTools affiche le résultat de l'exécution. Vous devriez voir une image semblable à celle ci-dessous dans la console :

Sortie de la console du programme fibonacci

Notre programme WebAssembly avec threads s'est bien exécuté. Nous vous encourageons à tester votre propre application multithread en suivant les étapes décrites ci-dessus.

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

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 une phase d'évaluation.

Les phases d'évaluation vous permettent de tester des fonctionnalités expérimentales avec vos utilisateurs en obtenant un jeton de test associé à 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 afin d'exécuter un test d'origine, remplissez ce formulaire.

Nous avons hébergé notre exemple simple ci-dessus à l'aide d'un jeton d'essai d'origine afin que vous puissiez l'essayer par vous-même sans avoir à créer quoi que ce soit.

Pour découvrir ce que quatre threads exécutés en parallèle peuvent apporter à l'art ASCII, vous devez également regarder cette démonstration.

Envoyez-nous vos commentaires.

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

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