Simple JavaScript mutex sharing

  • 2020-03-30 01:36:19
  • OfStack

There were several projects last year that required JavaScript mutexes, so I wrote several similar ones. Here's one of them:


//Published by Indream Luo
//Contact: indreamluo@qq.com
//Version: Chinese 1.0.0
!function ($) {
    window.indream = window.indream || {};
    $.indream = indream;
    indream.async = {
        //
        // The lock 
        //Lock: the number of the lock
        //Action: the method to execute after unlocking
        //
        lock: function (lock, action) {
            $.indream.async.waitings[lock] = $.indream.async.waitings[lock] || [];
            $.indream.async.waitings[lock].push(action);
            //If the lock is not used, the current action blocks the lock
            if (!$.indream.async.lockStatus[lock] && action) {
                $.indream.async.lockStatus[lock] = true;
                if (arguments.length > 2) {
                    var args = 'arguments[2]';
                    for (var i = 3; i < arguments.length; i++) {
                        args += ', arguments[' + i + ']';
                    }
                    eval('$.indream.async.action.call(action, ' + args + ')');
                } else {
                    $.indream.async.action.call(action);
                }
            }
        },
        //
        // unlock 
        //Lock: the number of the lock
        //
        releaseLock: function (lock) {
            $.indream.async.waitings[lock].shift();
            //If the wait queue has an object, the wait queue is executed, otherwise unlock
            if ($.indream.async.waitings[lock].length) {
                $.indream.async.waitings[lock][0]();
            } else {
                $.indream.async.lockStatus[lock] = false;
            }
        },
        //
        //The state of the lock
        //
        lockStatus: [],
        //
        //Wait for the event to complete
        //Lock: lock code, the same code will be integrated into a sequence, triggered at the same time
        //
        wait: function (lock, action) {
            $.indream.async.waitings[code] = $.indream.async.waitings[code] || [];
            $.indream.async.waitings[code].push(action);
        },
        //
        //Wait for the sequence
        //
        waitings: [],
        //
        //Data cache
        //
        action: {
            //
            //Monitoring and callback related methods
            //
            callback: {
                //
                // Listening to the 
                //
                listen: function (actionName, callback) {
                    var list = $.indream.async.action.callback.list;
                    list[actionName] = list[actionName] || [];
                    list[actionName].push(callback);
                },
                //
                // The callback 
                //
                call: function (actionName, args) {
                    var list = $.indream.async.action.callback.list;
                    if (list[actionName] && list[actionName].length) {
                        for (var i in list[actionName]) {
                            $.indream.async.action.call(list[actionName][i], args);
                        }
                    }
                },
                //
                //An existing list of callbacks
                //
                list: []
            },
            //
            //Choose the appropriate execution based on the existence of the method and the existence of the parameters
            //
            call: function (action) {
                if (action) {
                    if (arguments.length > 1) {
                        var args = 'arguments[1]';
                        for (var i = 2; i < arguments.length; i++) {
                            args += ', arguments[' + i + ']';
                        }
                        eval('action(' + args + ')');
                    } else {
                        action();
                    }
                }
            }
        }
    }
}(window.jQuery);

The elements of a mutex are:

The & # 8226; Lock and unlock
The & # 8226; Waiting queue
The & # 8226; Execution method
Usage of the above locks:


//Define the name of the lock
var lock = 'scrollTop()';
//Use the lock
$.indream.async.lock(lock, function () {
    var scrollTop = $(window).scrollTop();
    var timer;
    var fullTime = 100;
    for (timer = 0; timer <= fullTime; timer += 10) {
        setTimeout('$(window).scrollTop(' + (scrollTop * (fullTime - timer) / fullTime) + ');', timer);
    }
    //Release the lock
    setTimeout('$.indream.async.releaseLock("' + lock + '");', fullTime);
});

About this implementation, a brief explanation.

-spinlock or semaphore
JavaScript itself has no lock functionality, so locks are implemented at a high level.

According to the principle of JavaScript single thread, JS thread resources are very limited, it is not suitable to use spin lock, so we choose to use semaphore.

The implementation of a spin lock looks something like this, but with more do while:


while(true) {
    //do something...
}

Unfortunately, JS has only one thread available for execution, so this is not a good idea. Of course, if there is a need to choose a combination of setInterval and clearInterval to implement, it will also work well.

The semaphore approach is chosen here, and the principle is simple, as short as the code. The order of work is roughly:

The & # 8226; Push the code snippet (the callback action) into the wait queue
The & # 8226; Determines if the current lock is held, and if it is held, waits for release; otherwise, the lock is acquired and a callback is executed
The & # 8226; When the lock is released, shift out the next callback in the wait queue, pass the lock to it, and execute
 

- auto release or manual release
The most comfortable way to do this is of course to lock and then release when the current program is finished executing, but this is not easy because there are more situations where you need to customize the release scenario.

Locks are themselves used in asynchronous methods, so various types of asynchronous content often appear as well, such as AJAX and jQuery animations. At this point, auto-release doesn't meet the requirements, because the real "execution completion" happens after its internal asynchronous callbacks are complete, which is basically something only the developer can handle on his own, so the manual release is chosen.

However, there is still a drawback, is repeated release.

You can see that all locked objects are public, or should we say that all JS objects are public, unless local variables are isolated at the access level. However, the "lock" itself is a public resource, so there is no way to deal with it.

The optimization you can do here is to lock with a public lock name and unlock with a private lock ID, as setInterval and clearInterval did, to prevent duplicate releases. But it's not in the old code above, and it's probably going to be used soon.


Related articles: