最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - c-like mutex in nodejs - Stack Overflow

programmeradmin3浏览0评论

I am trying to achieve c-like wait and signal in node.js. I have in mind a binary semaphore or a mutex. Please keep in mind I am new to node.js and not very skilled in javascript.

My thing is :

  • I have a python interactive process running on the server.

  • A client makes a Ajax to the server, which is in turn asking this python process by writing on his stdin. The http response object is kept in a variable.

  • Then I am intercepting stdout which contains my answer, and sending it into the kept response object.

I would like to make this 'python part' atomic, which means :

  • If another request is received while the python process is already running, do a wait. Otherwise the request will be lost.

  • When the stdout triggers, send a signal to free the access.

Again, same behaviour as P and V in a binary semaphore or mutex lock and mutex unlock.

So if you advanced nodejs users have a suggestion ? I looked in npm but found nothing but Promise-like mutex, which I think does not really fit my situation.

Maybe I am wrong and there is a way. Anyway I look forward to reading your suggestions or workaround. Thank you.

Here is the relevant sample code.

route.js

var express = require('express');
var router = express.Router();
var pycaffe = require('./pycaffe');

var py = pycaffe.getInstance();

router.post('/getParams', function(req, res, next){
    in_data = req.body.imgBase64;
    py.queryParams(res, in_data);

});

pycaffe.js

const { spawn } = require('child_process');

var pycaffe = (function () {

    var pycaffeInstance;

    function create () {
        var response;  

        console.log('Loading nets...');

        const defaults = {
            cwd: './nets',
            env: process.env
        };

        var py = spawn('python', ['-i', 'deploy.py'], defaults);

        py.stdout.on('data', function(data){
            console.log(`stdout: ${data}`);
            var fig = JSON.parse(data);

            if(response !== undefined)
                //send http response
                response.send(fig)
                //mutex.unlock()
        });

        function queryParams(res, data) {
            //mutex.lock()
            response = res;
            py.stdin.write("query_params('"+data+"')\n");
        }

        return {
            queryParams: queryParams
        };
    }

    return {
        getInstance: function() {
            if(!pycaffeInstance) {
                pycaffeInstance = create();
            }
            return pycaffeInstance;
        }
    };
})();

module.exports = pycaffe;

I am trying to achieve c-like wait and signal in node.js. I have in mind a binary semaphore or a mutex. Please keep in mind I am new to node.js and not very skilled in javascript.

My thing is :

  • I have a python interactive process running on the server.

  • A client makes a Ajax to the server, which is in turn asking this python process by writing on his stdin. The http response object is kept in a variable.

  • Then I am intercepting stdout which contains my answer, and sending it into the kept response object.

I would like to make this 'python part' atomic, which means :

  • If another request is received while the python process is already running, do a wait. Otherwise the request will be lost.

  • When the stdout triggers, send a signal to free the access.

Again, same behaviour as P and V in a binary semaphore or mutex lock and mutex unlock.

So if you advanced nodejs users have a suggestion ? I looked in npm but found nothing but Promise-like mutex, which I think does not really fit my situation.

Maybe I am wrong and there is a way. Anyway I look forward to reading your suggestions or workaround. Thank you.

Here is the relevant sample code.

route.js

var express = require('express');
var router = express.Router();
var pycaffe = require('./pycaffe');

var py = pycaffe.getInstance();

router.post('/getParams', function(req, res, next){
    in_data = req.body.imgBase64;
    py.queryParams(res, in_data);

});

pycaffe.js

const { spawn } = require('child_process');

var pycaffe = (function () {

    var pycaffeInstance;

    function create () {
        var response;  

        console.log('Loading nets...');

        const defaults = {
            cwd: './nets',
            env: process.env
        };

        var py = spawn('python', ['-i', 'deploy.py'], defaults);

        py.stdout.on('data', function(data){
            console.log(`stdout: ${data}`);
            var fig = JSON.parse(data);

            if(response !== undefined)
                //send http response
                response.send(fig)
                //mutex.unlock()
        });

        function queryParams(res, data) {
            //mutex.lock()
            response = res;
            py.stdin.write("query_params('"+data+"')\n");
        }

        return {
            queryParams: queryParams
        };
    }

    return {
        getInstance: function() {
            if(!pycaffeInstance) {
                pycaffeInstance = create();
            }
            return pycaffeInstance;
        }
    };
})();

module.exports = pycaffe;
Share Improve this question edited Feb 1, 2018 at 13:49 Bastien Meta asked Feb 1, 2018 at 13:31 Bastien MetaBastien Meta 331 silver badge4 bronze badges 2
  • How do you know when the python instance returned a response? Does it write sth to stdout ? – Jonas Wilms Commented Feb 1, 2018 at 14:09
  • yes it does, and what it writes is sent in http response – Bastien Meta Commented Feb 1, 2018 at 15:36
Add a ment  | 

2 Answers 2

Reset to default 9

You don’t need a mutex or semaphore, cause in node you have only one thread. All you need is to store the requests in a queue (which can be an array) if there is any request being processed. When you receive a response via stdout, check if the queue contains another request and process.

You can have an mutex-like abstraction based on promises. Here is an example:

class Mutex {
    constructor () {
        this.queue = [];
        this.locked = false;
    }

    lock () {
        return new Promise((resolve, reject) => {
            if (this.locked) {
                this.queue.push([resolve, reject]);
            } else {
                this.locked = true;
                resolve();
            }
        });
    }

    release () {
        if (this.queue.length > 0) {
            const [resolve, reject] = this.queue.shift();
            resolve();
        } else {
            this.locked = false;
        }
    }
}

Usage example:

const mutex = new Mutex();

// using promise syntax
const handleRequest = () => {
    mutex.lock().then(() => {
        // do something here

        mutex.release();
    })
};

// using async syntax
const handleRequest = async () => {
    await mutex.lock();

    // do something here

    mutex.release();
};

I used this code to test:

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

let processNumber = 1;
const startProcess = async (mutex) => {
    const thisProcessId = processNumber++;
    console.log(new Date(), `started process ${thisProcessId}`);
    await mutex.lock();
    console.log(new Date(), `after lock ${thisProcessId}`);
    await delay(3000);
    mutex.release();
    console.log(new Date(), `finished process ${thisProcessId}`);
};

(() => {
    const mutex = new Mutex();
    for (let i = 0; i < 5; i++) {
        setTimeout(() => startProcess(mutex), Math.random() * 10000);
    }
})();

... and got this result:

2018-02-01T19:02:36.418Z 'started process 1'
2018-02-01T19:02:36.421Z 'after lock 1'
2018-02-01T19:02:38.565Z 'started process 2'
2018-02-01T19:02:39.426Z 'finished process 1'
2018-02-01T19:02:39.426Z 'after lock 2'
2018-02-01T19:02:40.048Z 'started process 3'
2018-02-01T19:02:42.309Z 'started process 4'
2018-02-01T19:02:42.428Z 'finished process 2'
2018-02-01T19:02:42.428Z 'after lock 3'
2018-02-01T19:02:43.200Z 'started process 5'
2018-02-01T19:02:45.429Z 'finished process 3'
2018-02-01T19:02:45.429Z 'after lock 4'
2018-02-01T19:02:48.433Z 'finished process 4'
2018-02-01T19:02:48.433Z 'after lock 5'
2018-02-01T19:02:51.438Z 'finished process 5'

One option would be to spawn multiple python processes:

router.post('/getParams', function(req, res, next){
  in_data = req.body.imgBase64;
  pycaffe.create().queryParams(res, in_data);
});

For that you need to expose create:

return {
    getInstance: function() {
        if(!pycaffeInstance) {
            pycaffeInstance = create();
        }
        return pycaffeInstance;
    },
    create // expose create here
};

Alternatively, if you really only want one python process, you should use an asynchronous queue instead of a mutex lock. There is no mutex lock in nodejs as there is no parallel code execution. A sample async queue would look like this:

 class AsyncQueue {
   constructor(task) {
     this.task = task;
     this.queue = [];
  }

  push(el){
   this.queue.push(el);
   this.start();
  }

  async start(){
   if(this.running) return;
   this.running = true;

   let el;
   while(el = this.queue.shift()){
      await this.task(el);
  }

  this.running = false;
 }
}

And that can be used like this:

function pyRequest({res, data}){
  py.requestParams(res, data);

  return new Promise(resolve => res.on("data", resolve));
}

const pyQueue = new AsyncQueue(pyRequest);

router.post('/getParams', function(req, res, next){
  pyQueue.push({
    data: req.body.imgBase64,
    res
  });
});
发布评论

评论列表(0)

  1. 暂无评论