Userspace Threading in JavaScript

There was an interesting Daily Programmer problem posted a couple of weeks ago: write a userspace threading library. I decided to do it in JavaScript, building it on top of setTimeout. Remember that JavaScript is single-threaded by specification, so this will be a nonpreemptive, cooperative system.

Start by creating the Thread prototype. As thread constructors usually work, it accepts the function to be run in that thread.

function Thread(f) {
    this.alive = true;
    this.schedule(f);
}

The schedule method schedules a function to be run in that thread. It’s not really meant for users to use directly. I’ll define it in a moment.

Only one thread actually runs at a time, so globally keep track of the which one is running at the moment.

Thread.current = null;

Now here’s the core method that makes everything work, runner. It accepts a function of arbitrary arity and returns a function that runs the provided function in this thread.

Thread.prototype.runner = function(f) {
    var _this = this;
    return function() {
        if (_this.alive) {
            try {
                Thread.current = _this;
                f.apply(this, arguments);
            } finally {
                Thread.current = null;
            }
        }
    };
};

The runner sets the current thread to the proper value, calls the function, then clears the current thread. If the thread is no longer active, nothing happens.

With that in place, schedule is defined like this,

Thread.prototype.schedule = function(f) {
    setTimeout(this.runner(f), 0);
};

It creates a runner function for f and schedules it to run as soon as possible on JavaScript’s event loop using setTimeout. Queuing up on the event loop is the cooperative part of all this. Other threads and events may already be queued with a timeout of 0, so they run first.

Technically this is all that’s needed. To yield, schedule a function and return.

function() {
    // ... do some work ...
    Thread.current.schedule(function() {
        // ... do more work ...
    });
}

I don’t want the user to need to think about Thread.current, so here’s a convenience yield function.

Thread.yield = function(f) {
    Thread.current.schedule(f);
};

Now to use it,

function() {
    // ... do some work ...
    Thread.yield(function() {
        // ... do more work ...
    });
}

Halting a thread is easy. Any scheduled functions for this thread will not be invoked, as specified in the runner method.

Thread.prototype.destroy = function() {
    this.alive = false;
};

There’s one more situation to worry about: callbacks. Imagine an asynchronous storage API.

// ... in thread context ...
storage.getValue(function(value) {
    // doesn't run in thread context
});

In order to run in the thread the library user would need to create a runner function for the current thread. To avoid making them worry about Thread.current and runner, provide another convenience function, wrap. There may be a better name for it, but I couldn’t think of it.

Thread.wrap = function(f) {
    return Thread.current.runner(f);
};

Fixing the callback,

// ... in thread context ...
storage.getValue(Thread.wrap(function(value) {
    // ... also in thread context ...
}));

Threading Demo

To demonstrate threading I’ll make a thread that continuously fetches random numbers from a server and displays them.

Here’s a simple-httpd servlet for generating numbers. The route for this servlet will be /random.

(defservlet random text/plain ()
  (princ (random* 1.0)))

Since I’m doing this interactively with Skewer on the blank demo page, make a tag for displaying the number.

var h1 = document.createElement('h1');
document.body.appendChild(h1);

Here’s the function that will run in the thread. It fetches a number asynchronously, displays it, then recurses. Notice that Thread.yield() acts like a trampoline, providing free tail-call optimization! This is because the stack is cleared before the provided function is invoked.

function random() {
    var xhr = new XMLHttpRequest();
    xhr.open('GET', '/random', true);
    xhr.send();
    xhr.onload = Thread.wrap(function() {
        h1.innerHTML = xhr.responseText;
        Thread.yield(random);
    });
};

I set onload after calling send just for code organization purposes. That code is evaluated after send is called. As far as I know this should work fine.

Now to create a thread!

var foo = new Thread(random);

The heading flashes with random numbers as soon as the thread is created. Even though this thread is continuously running, it’s frequently yielding. Everything remains responsive, including the ability to stop the thread.

foo.destroy();

As soon as this is evaluated, the heading stops being updated. I think that’s pretty neat!

Performance

I haven’t tested performance, but I imagine it’s awful. Especially because of that frequent use of the apply method. You wouldn’t want CPU-intensive operations to cooperate like this. Fortunately, in my demo above I’m manipulating the DOM and waiting on a server response, so the performance penalties of threading should be negligible.

Have a comment on this article? Start a discussion in my public inbox by sending an email to ~skeeto/public-inbox@lists.sr.ht [mailing list etiquette] , or see existing discussions.

This post has archived comments.

null program

Chris Wellons

wellons@nullprogram.com (PGP)
~skeeto/public-inbox@lists.sr.ht (view)