WebAssembly-Threads in Chrome 70 ausprobieren

Die Unterstützung für WebAssembly-Threads wurde in Chrome 70 im Rahmen eines Ursprungstests bereitgestellt.

Alex Danilo

WebAssembly (Wasm) ermöglicht die Kompilierung von Code, der in C++ und anderen Sprachen geschrieben wurde, um im Web ausgeführt zu werden. Eine sehr nützliche Funktion von nativen Anwendungen ist die Möglichkeit, Threads zu verwenden – ein Element für parallele Berechnungen. Die meisten C- und C++-Entwickler sind mit pthreads vertraut, einer standardisierten API für die Threadverwaltung in einer Anwendung.

Die WebAssembly Community Group arbeitet daran, Threads ins Web zu bringen, um echte Multi-Thread-Anwendungen zu ermöglichen. Im Rahmen dieser Bemühungen hat V8 die erforderliche Unterstützung für Threads in der WebAssembly-Engine implementiert, die über einen Origin Trial verfügbar ist. Mit Origin Trials können Entwickler neue Webfunktionen testen, bevor sie vollständig standardisiert sind. So erhalten wir wertvolles Feedback von mutigen Entwicklern, das für die Validierung und Verbesserung neuer Funktionen unerlässlich ist.

Die Chrome-Version 70 unterstützt Threads für WebAssembly und wir ermutigen interessierte Entwickler, diese zu verwenden und uns Feedback zu geben.

Threads? Was ist mit Workern?

Browser unterstützen die Parallelität über Web Worker in Chrome 4 seit 2012. Es ist normal, dass Begriffe wie "im Hauptthread" usw. zu hören sind. Web Worker teilen jedoch keine veränderlichen Daten zwischen ihnen, sondern nutzen für die Kommunikation die Nachrichtenweitergabe. Tatsächlich weist Chrome jedem von ihnen eine neue V8-Engine zu (sogenannte Isolate). Isolate teilen weder kompilierten Code noch JavaScript-Objekte und können daher keine veränderbaren Daten wie pthreads teilen.

WebAssembly-Threads hingegen können denselben Wasm-Speicher gemeinsam nutzen. Der zugrunde liegende Speicher des gemeinsamen Arbeitsspeichers wird mit einem SharedArrayBuffer realisiert, einer JavaScript-Primitivstruktur, die es ermöglicht, den Inhalt eines einzelnen ArrayBuffers gleichzeitig zwischen Workern zu teilen. Jeder WebAssembly-Thread wird in einem Web Worker ausgeführt, aber dank des gemeinsamen Wasm-Arbeitsspeichers können sie ähnlich wie auf nativen Plattformen arbeiten. Das bedeutet, dass die Anwendungen, die Wasm-Threads verwenden, wie bei jeder herkömmlichen Anwendung mit Threads für die Verwaltung des Zugriffs auf den gemeinsamen Speicher verantwortlich sind. Es gibt viele vorhandene in C oder C++ geschriebene Codebibliotheken, die pthreads verwenden. Diese können in Wasm kompiliert und im Echtthread-Modus ausgeführt werden, sodass mehr Kerne gleichzeitig mit denselben Daten arbeiten können.

Ein einfaches Beispiel

Hier ist ein Beispiel für ein einfaches C-Programm, das Threads verwendet.

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

Dieser Code beginnt mit der Funktion main(), die zwei Variablen deklariert: fg_val und bg_val. Außerdem gibt es eine Funktion namens fibonacci(), die in diesem Beispiel von beiden Threads aufgerufen wird. Die Funktion main() erstellt mit pthread_create() einen Hintergrund-Thread, der den Wert der Fibonacci-Zahlenfolge berechnet, der dem Wert der Variablen bg_val entspricht. In der Zwischenzeit berechnet die Funktion main(), die im Vordergrund-Thread ausgeführt wird, den Wert für die Variable fg_val. Sobald der Hintergrund-Thread beendet ist, werden die Ergebnisse ausgegeben.

Für Thread-Unterstützung kompilieren

Zuerst sollten Sie das emscripten SDK installiert haben, vorzugsweise Version 1.38.11 oder höher. Damit unser Beispielcode mit aktivierten Threads für die Ausführung im Browser erstellt werden kann, müssen wir dem emscripten-emcc-Compiler einige zusätzliche Flags übergeben. Unsere Befehlszeile sieht so aus:

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

Mit dem Befehlszeilenargument „-s USE_PTHREADS=1“ wird die Threadunterstützung für das kompilierte WebAssembly-Modul aktiviert. Das Argument „-s PTHREAD_POOL_SIZE=2“ weist den Compiler an, einen Pool mit zwei (2) Threads zu generieren.

Wenn das Programm ausgeführt wird, wird das WebAssembly-Modul geladen, für jeden Thread im Threadpool ein Web Worker erstellt und das Modul für jeden Worker freigegeben. In diesem Fall sind es zwei, die verwendet werden, wenn pthread_create() aufgerufen wird. Jeder Worker instanziiert das Wasm-Modul mit demselben Arbeitsspeicher, damit er kooperieren kann. Die neuesten Änderungen an V8 in Version 7.0 teilen den kompilierten nativen Code von Wasm-Modulen, die zwischen Workern übergeben werden. So können auch sehr große Anwendungen auf viele Worker skaliert werden. Hinweis: Es ist sinnvoll, dafür zu sorgen, dass die Größe des Threadpools der maximalen Anzahl von Threads entspricht, die Ihre Anwendung benötigt. Andernfalls kann die Threaderstellung fehlschlagen. Wenn der Threadpool zu groß ist, erstellen Sie gleichzeitig unnötige Web Worker, die ausschließlich Arbeitsspeicher belegen.

So kannst du es ausprobieren

Am schnellsten können Sie unser WebAssembly-Modul testen, indem Sie die experimentelle Unterstützung von WebAssembly-Threads in Chrome 70 und höher aktivieren. Rufen Sie die URL about://flags in Ihrem Browser auf (siehe unten):

Seite mit Chrome-Flags

Suchen Sie als Nächstes die Einstellung für experimentelle WebAssembly-Threads. Sie sieht so aus:

Einstellung für WebAssembly-Threads

Ändern Sie die Einstellung wie unten gezeigt zu Aktiviert und starten Sie dann den Browser neu.

Einstellung für WebAssembly-Threads aktiviert

Nachdem der Browser neu gestartet wurde, können wir versuchen, das gethreadete WebAssembly-Modul mit einer minimalen HTML-Seite zu laden, die nur diesen Inhalt enthält:

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

Wenn Sie diese Seite ausprobieren möchten, müssen Sie eine Art Webserver ausführen und die Seite über Ihren Browser laden. Dadurch wird das WebAssembly-Modul geladen und ausgeführt. Wenn Sie die DevTools öffnen, sehen Sie die Ausgabe des Laufs. In der Konsole sollte ungefähr das folgende Ausgabebild angezeigt werden:

Konsolenausgabe des Fibonacci-Programms

Unser WebAssembly-Programm mit Threads wurde erfolgreich ausgeführt. Wir empfehlen Ihnen, die oben beschriebenen Schritte auszuführen, um Ihre eigene Anwendung mit Threads auszuprobieren.

Tests im Feld mit einem Ursprungstest

Für die Entwicklung ist es in Ordnung, Threads durch Aktivieren von experimentellen Flags im Browser auszuprobieren. Wenn Sie Ihre Anwendung jedoch in der Praxis testen möchten, können Sie dazu einen sogenannten Ursprungstest verwenden.

Mit Origin Trials können Sie experimentelle Funktionen mit Ihren Nutzern testen. Dazu müssen Sie ein Testtoken abrufen, das mit Ihrer Domain verknüpft ist. Anschließend können Sie Ihre Anwendung bereitstellen und davon ausgehen, dass sie in einem Browser funktioniert, der die getestete Funktion unterstützt (in diesem Fall Chrome 70 und höher). Wenn Sie ein eigenes Token für einen Ursprungstest benötigen, verwenden Sie dieses Antragsformular.

Wir haben das einfache Beispiel oben mit einem Ursprungstesttoken gehostet, sodass Sie es selbst ausprobieren können, ohne etwas erstellen zu müssen.

Wenn Sie sehen möchten, was vier parallel ausgeführte Threads für ASCII-Art tun können, sollten Sie sich auch diese Demo ansehen.

Feedback geben

WebAssembly-Threads sind eine äußerst nützliche neue Primitive für die Portierung von Anwendungen in das Web. Es ist jetzt möglich, C- und C++-Anwendungen und -Bibliotheken, für die pthreads-Unterstützung erforderlich ist, in der WebAssembly-Umgebung auszuführen.

Wir freuen uns über Feedback von Entwicklern, die diese Funktion ausprobieren. So können wir den Standardisierungsprozess besser planen und die Nützlichkeit der Funktion überprüfen. Am besten gibst du Feedback, indem du Probleme meldest und/oder dich in den Standardisierungsprozess in der WebAssembly Community Group einleitest.