Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mutex in JavaScript - does this look like a correct implementation?

Not an entirely serious question, more of a shower thought: JavaScript's await keyword should allow for something that feels an awful lot like a mutex in your average "concurrent language".

function Mutex() {
    var self = this; // still unsure about how "this" is captured
    var mtx = new Promise(t => t()); // fulfilled promise ≡ unlocked mutex
    this.lock = async function() {
        await mtx;
        mtx = new Promise(t => {
            self.unlock = () => t();
        });
    }
}
// Lock
await mutex.lock();
// Unlock
mutex.unlock();

Is this a correct implementation (apart from proper error handling)? And… can I have C++-RAII-style lock guards?

like image 698
Caesar Avatar asked Jun 28 '18 15:06

Caesar


People also ask

How a mutex is implemented?

When using a counter, it can become a Semaphore. A mutex is the starting point for a critical section, which uses a mutex internally to see if it can enter a section of code. If the mutex is free, it sets the mutex and executes the code, only to release the mutex when done.

What is Javascript mutex?

What is a mutex? A mutex is an entity that allows to synchronize multiple concurrent processes by locking and waiting.

How lock is implemented in Javascript?

User clicks button B . B raises an onclick event. If B is in event-state the event waits for B to be in ready-state before propagating. If B is in ready-state , B is locked and is set to event-state , then the event propagates.

Do I need mutex for Javascript?

That means that everything that happens in an event must be finished before the next event will be processed. That being said, you may need a mutex if your code does something where it expects a value not to change between when the asynchronous event was fired and when the callback was called.


1 Answers

Your implementation allows as many consumers obtain the lock as ask for it; each call to lock waits on a single promise:

function Mutex() {
    var self = this; // still unsure about how "this" is captured
    var mtx = new Promise(t => t()); // fulfilled promise ≡ unlocked mutex
    this.lock = async function() {
        await mtx;
        mtx = new Promise(t => {
            self.unlock = () => t();
        });
    }
}

const mutex = new Mutex();

(async () => {
  await Promise.resolve();
  await mutex.lock();
  console.log("A got the lock");
})();
(async () => {
  await Promise.resolve();
  await mutex.lock();
  console.log("B got the lock");
})();

You'd need to implement a queue of promises, creating a new one for each lock request.

Side notes:

  • new Promise(t => t()) can be more simply and idiomatically written Promise.resolve() :-)
  • No need for self if you're using arrow functions like that; arrow functions close over the this where they're created (exactly like closing over a variable)
  • Probably would make sense for unlock to be a resolution value of the lock promise, so only the code that obtained the lock can release it

Something like this:

function Mutex() {
    let current = Promise.resolve();
    this.lock = () => {
        let _resolve;
        const p = new Promise(resolve => {
            _resolve = () => resolve();
        });
        // Caller gets a promise that resolves when the current outstanding
        // lock resolves
        const rv = current.then(() => _resolve);
        // Don't allow the next request until the new promise is done
        current = p;
        // Return the new promise
        return rv;
    };
}

Live Example:

"use strict";
function Mutex() {
    let current = Promise.resolve();
    this.lock = () => {
        let _resolve;
        const p = new Promise(resolve => {
            _resolve = () => resolve();
        });
        // Caller gets a promise that resolves when the current outstanding
        // lock resolves
        const rv = current.then(() => _resolve);
        // Don't allow the next request until the new promise is done
        current = p;
        // Return the new promise
        return rv;
    };
}

const rand = max => Math.floor(Math.random() * max);

const delay = (ms, value) => new Promise(resolve => setTimeout(resolve, ms, value));

const mutex = new Mutex();

function go(name) {
    (async () => {
        console.log(name + " random initial delay");
        await delay(rand(50));
        console.log(name + " requesting lock");
        const unlock = await mutex.lock();
        console.log(name + " got lock");
        await delay(rand(1000));
        console.log(name + " releasing lock");
        unlock();
    })();
}
go("A");
go("B");
go("C");
go("D");
.as-console-wrapper {
  max-height: 100% !important;
}
like image 108
T.J. Crowder Avatar answered Nov 05 '22 16:11

T.J. Crowder