ARTICLE

Threading with Web Workers

From WebAssembly in Action by C. Gerard Gallant

This article covers:

§ Using a web worker to fetch and compile a WebAssembly module

§ Instantiating a WebAssembly module on behalf of Emscripten’s JavaScript code

__________________________________________________________________

Take 37% off WebAssembly in Action. Just enter fccgallant into the discount code box at checkout at manning.com.
__________________________________________________________________

In this article, you’re going to learn about the different options for using threads in a browser with relation to WebAssembly modules.

INFO: A thread is a path of execution within a process, and a process can have multiple threads. A pthread, also known as a POSIX thread, is an API defined by the POSIX.1c standard for an execution module which is independent of programming language: https://en.wikipedia.org/wiki/POSIX_Threads. For more on pthreads, you’re going to have to pick up a copy of the book.

By default, both the UI and JavaScript of a webpage operate in a single thread. If your code does too much processing without yielding to the UI periodically, it can cause the UI to become unresponsive. Your animations freeze and the controls on the webpage won’t respond to a user’s input which can be frustrating for a user.

If the webpage remains unresponsive for long enough (typically ten seconds), a browser may prompt the user to see if they want to stop the page, as shown in figure 1. If the user stops the script on your webpage, your webpage may no longer function as expected unless the user refreshes it.

Image for post
Image for post
Figure 1. A long-running process has caused Firefox to become unresponsive. The browser is prompting the user if they want to terminate the script.

TIP: to keep webpages as responsive as possible, whenever you interact with a Web API that has both synchronous and asynchronous functions, it’s a best practice to use the asynchronous functions.

Sometimes doing some heavy processing without interfering with the UI is desired, and browser makers came up with something called web workers.

Benefits of web workers

What do web workers do and why would you want to use them? They bring the ability to create background threads to browsers. As shown in figure 2, web workers allow you to run JavaScript in a thread which is separate from the UI thread, and communication between the two is accomplished by passing messages.

Image for post
Image for post
Figure 2. Your JavaScript creates a web worker and then communicates with the web worker by passing messages.

Unlike the UI thread, it’s permitted to use synchronous functions in a web worker, if desired, because doing this doesn’t block the UI thread. Within a worker, you can spawn additional workers and you have access to many of the same items that you have access to in the UI thread like fetch, WebSockets, and IndexedDB. For a complete list of APIs available to web workers, you can visit the following webpage: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers

Another advantage of web workers is that most devices now have multiple cores. If you’re able to split up your processing across several threads, the length of time it takes to complete the processing should decrease. Web workers are also supported in nearly all web browsers, including mobile browsers.

WebAssembly modules can make use of web workers in several ways:

Worker API You need to create two C or C++ files in order to compile one to run in the main thread and one to run in the web worker. The web worker file needs to be compiled with the -s BUILD_AS_WORKER=1 flag set.

Considerations for using web workers

You’ll learn to use web workers shortly, but you should be aware of the following:

Pre-fetch a WebAssembly module using a web worker

Suppose you have a webpage that needs a WebAssembly module after the page has loaded. Rather than download and instantiate the module as the webpage is loading, you decide to defer the download until after the page has loaded to keep the page load time as fast as possible. To keep your webpage as responsive as possible, you also decide to use a web worker to handle downloading and compiling the WebAssembly module on a background thread.

As illustrated in figure 3, in this section you’ll learn how to do the following:

Image for post
Image for post
Figure 3. Your JavaScript creates a web worker. The worker downloads and compiles the WebAssembly module and then passes the compiled module to the main UI thread. Emscripten uses the compiled module rather than downloading the module itself.

The following steps enumerate the solution for this scenario (figure 4):

Image for post
Image for post
Figure 4. The steps to implement the pre-fetch scenario. Adjust calculate_primes.cpp to determine how long the computations take. Instruct Emscripten to generate the WebAssembly files and then create the HTML and JavaScript files. The JavaScript creates a web worker to download and compile the WebAssembly module. Finally, the compiled module is passed back to the webpage where it’s instantiated by your code instead of Emscripten’s JavaScript.

The first step, shown in figure 5, is to adjust the calculate primes logic to determine how long it takes to do the calculations:

Image for post
Image for post
Figure 5. Adjust the calculate primes logic to determine how long the calculations take

Adjusting the calculate primes logic

Let’s get started. In your WebAssembly\ folder create a Chapter 9\9.3 pre-fetch\source\ folder.

Copy the calculate_primes.cpp file from your Chapter 7\7.2.2 dlopen\source\ folder to your newly created source\ folder. Open the calculate_primes.cpp file with your favorite editor.

For this scenario, you’ll be using a vector class which is defined in the vector header to hold the list of prime numbers found within the range specified. You’ll also use the high_resolution_clock class, defined in the chrono header, to time how long it takes for your code to determine the prime numbers.

Add the includes for the vector and chrono headers following the cstdio header in the calculate_primes.cpp file as shown in the following code snippet:

#include <vector>
#include <chrono>

Now remove the EMSCRIPTEN_KEEPALIVE declaration from above the FindPrimes function, because this function won’t be called from outside the module.

Rather than call printf for every prime number as it’s found, you’re going to modify the logic in the FindPrimes function to add the prime number to a vector object instead. This is done to determine the execution duration of the calculations themselves without the delay due to a call to the JavaScript code on every loop. The main function is then adjusted to handle sending the prime number information to the browser’s console window.

VECTOR: A vector object is a sequence container for dynamic sized arrays where the storage is automatically increased or decreased as needed. More information on the vector object can be found here: https://en.cppreference.com/w/cpp/container/vector

The modifications that you’ll make to the FindPrimes function are the following:

Adjust the FindPrimes function, in your calculate_primes.cpp file, to match the code in the following snippet:

void FindPrimes(int start, int end,
std::vector<int>& primes_found) { #A
for (int i = start; i <= end; i += 2) {
if (IsPrime(i)) {
primes_found.push_back(i); #B
}
}
}

#A A vector pointer parameter has been added

#B The prime number is added to the list

Your next step is to modify the main function to:

Your main function in your calculate_primes.cpp file should now look like the code in listing 1.

Listing 1 The main function in calculate_primes.cpp



int main() {
int start = 3, end = 1000000;
printf("Prime numbers between %d and %d:\n", start, end);

std::chrono::high_resolution_clock::time_point duration_start =
std::chrono::high_resolution_clock::now(); #A

std::vector<int> primes_found;
FindPrimes(start, end, primes_found); #B

std::chrono::high_resolution_clock::time_point duration_end =
std::chrono::high_resolution_clock::now(); #C

std::chrono::duration<double, std::milli> duration =
(duration_end - duration_start); #D

printf("FindPrimes took %f milliseconds to execute\n", duration.count());

printf("The values found:\n");
for(int n : primes_found) { #E
printf("%d ", n);
}
printf("\n");

return 0;
}

#A Get the current time to mark the start of the FindPrimes execution

#B Create a vector object which holds integers and passes it to the FindPrimes function

#C Get the current time to mark the end of the FindPrimes execution

#D Determine the amount of time, in milliseconds, it took FindPrimes to execute

#E Loop through each value in the vector object and output the value to the console

Now that the calculate_primes.cpp file has been modified, the second step (figure 6) is where you’ll have Emscripten generate the WebAssembly files.

Image for post
Image for post
Figure 6. Use Emscripten to generate the WebAssembly files from calculate_primes.cpp

That’s all for now. If you want to learn more about the book, check it out on liveBook here and see this slide deck.

Written by

Follow Manning Publications on Medium for free content and exclusive discounts.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store