如何在node.js中创建后台单线程FIFO作业队列

时间:2016-05-06 11:05:19

标签: javascript node.js concurrency

我是一名JS / node初学者,我试图在Javascript中解决“并发”问题。我觉得我现在对回调很满意,但我不认为这是我的方案中的方法。基本上我有重复的昂贵任务(worker),我需要在main process继续工作的同时逐一处理。这是一个最小的测试。

/*
 * We have a worker function  that does some expensive task, e.g., an
 * I/O task or something else.
 */
worker = function ( jobId ) {

    // It will take something between 1 and 2 seconds.
    var runtime = Math.floor((Math.random() * 1000) + 1000);
    console.log("started job #" + jobId + " (" + runtime + " ms)");

    // Then the worker will do something for a while ...
    setTimeout(
        function() {
            // .. and at some point it'll be finished.
            console.log("finished job #" + jobId);
        }, runtime
    );

};

/*
 * We obviously have a main process that meanwhile does other stuff
 * like processed user interactions. In this case we call this until
 * some artificial tickets are used.
 */
mainprocess = function (tickets) {

    // Simulate some processing time ..
    var runtime = Math.floor((Math.random() * 500));
    setTimeout(
        function() {
            console.log("main process #" + tickets + " (" + runtime + " ms)");
            if (tickets > 0) {
                tickets--;
                mainprocess(tickets);
            }
        }, runtime
    );
}

// At some point in the code we create workers and we want to make sure
// they're processed in the *order of creation* and *one after another* 
// without blocking the main process ...
for ( var i = 1; i <= 10; i++) {
    worker(i);
};

// ... and the some other stuff will happen for a while!
mainprocess(10);

// ..

代码目前输出类似..

started job #1 (1751 ms)
started job #2 (1417 ms)
...
started job #9 (1050 ms)
started job #10 (1864 ms)
main process #10 (142 ms)
main process #9 (228 ms)
main process #8 (149 ms)
main process #7 (88 ms)
main process #6 (410 ms)
finished job #9
finished job #5
main process #5 (265 ms)
finished job #2
main process #4 (270 ms)
finished job #7
finished job #3
finished job #1
...
main process #1 (486 ms)
main process #0 (365 ms)

我真的不知道,如何更改代码,以便主进程将继续,而工作线程以创建顺序执行(目前只以正确的顺序启动)和一个接一个(目前全部并行)。那么期望的输出将是..

started job #1 (1384 ms)
main process #10 (268 ms)
main process #9 (260 ms)
main process #8 (216 ms)
main process #7 (93 ms)
main process #6 (160 ms)
main process #5 (269 ms)
main process #4 (44 ms)
finished job #1
started job #2 (1121 ms)
main process #3 (172 ms)
main process #2 (170 ms)
main process #1 (437 ms)
finished job #2
started job #3 (1585 ms)
main process #0 (460 ms)
finished job #3
started job #4 (1225 ms)
finished job #4
started job #5 (1300 ms)
finished job #5

任何帮助都将不胜感激。

2 个答案:

答案 0 :(得分:1)

好的,我已经明白了。扩展和评论的代码使用 javascript的内置承诺,但您可以使用QBluebird或任何其他节点兼容的promise库实现相同的功能。请注意,jquery $.Deferred对象在节点环境中不可用。

/*
 * We have a worker function  that does some expensive task, e.g., an
 * I/O task or something else.
 */
worker = function ( jobId ) {

    // we need to put it into a new promise object
    return new Promise(function(resolve, reject) {

        // It will take something between 1 and 2 seconds.
        var runtime = Math.floor((Math.random() * 1000) + 1000);
        console.log("started job #" + jobId + " (" + runtime + " ms)");

        // Then the worker will do something for a while ...
        setTimeout(
            function() {
                // .. and at some point it'll be finished.
                console.log("finished job #" + jobId);
                 // .. now we have to resolve the promise!!
                resolve("resolved job #" + jobId);
            }, runtime
        );

    });
};


/*
 * We obviously have a main process that meanwhile does other stuff
 * like processed user interactions. In this case we call this until
 * some artificial tickets are used.
 */
mainprocess = function (tickets) {

    // Simulate some processing time ..
    var runtime = Math.floor((Math.random() * 500));
    setTimeout(
        function() {
            console.log("main process #" + tickets + " (" + runtime + " ms)");
            if (tickets > 0) {
                tickets--;
                mainprocess(tickets);
            }
        }, runtime
    );
}

// create a sequence with a resolved promise
var sequence = Promise.resolve();

// At some point in the code we create workers and we want to make sure
// they're processed in the *order of creation* and *one after another*
// without blocking the main process ...
for ( var i = 1; i <= 10; i++) {
    // create an IIFE so that the current "i" gets its own
    // closure when it will be used later (otherwise all job ids
    // would be "11" on invokation of the worker).
    (function() {
        var jobId = i;
        // add a new promise after the previous promise resolved
        sequence = sequence.then(
            function(result) {
                // handle result later
                return worker(jobId);
                // return just added the next promise to the chain!
            },
            function(err) {
                // handle error later
            }
        );
    })(); // END IIFE
};

// ... and the some other stuff will happen for a while!
mainprocess(10);

// ..

输出符合要求:

started job #1 (1384 ms)
main process #10 (268 ms)
main process #9 (260 ms)
main process #8 (216 ms)
main process #7 (93 ms)
main process #6 (160 ms)
main process #5 (269 ms)
main process #4 (44 ms)
finished job #1
started job #2 (1121 ms)
main process #3 (172 ms)
main process #2 (170 ms)
main process #1 (437 ms)
finished job #2
started job #3 (1585 ms)
main process #0 (460 ms)
finished job #3
started job #4 (1225 ms)
finished job #4
started job #5 (1300 ms)
finished job #5
...

我必须对以下文章给予高度赞赏,这些文章提供了见解:

答案 1 :(得分:0)

如果您希望主线程首先运行,然后按工作线程创建顺序运行,请将for循环放在您调用worker(i)的设置Timeout函数中,并给出大约1000ms的恒定延迟。然后,完成的作业也应该被给予1000ms的恒定延迟,以便它们按照它们的创建顺序执行。 查看链接:jsfiddle.net/som99/weq87xnd/2/