web-dev-qa-db-ja.com

node.jsコードは競合状態を引き起こす可能性がありますか?

私が読んだところによると、さまざまなスレッドが共有変数を変更しようとすると、競合状態が発生します。その結果、これらのスレッドの実行の逐次順序では不可能な値になる可能性があります。

しかし、node.jsのコードは単一のスレッドで実行されるので、node.jsで記述されたコードには競合状態がないということですか?

23
Jatin

はい。リソースの共有を開始するとすぐに、Node.jsが競合状態になる可能性があります。

Node.jsはシングルスレッドの性質を持っているため、Node.jsで競合状態を取得できないと思いましたが、ノードの外部の共有リソース(ファイルシステムのファイルなど)を使用するとすぐに競合状態になる可能性があります。これを理解しようとしたときに、この問題の例をこの質問に投稿しました: node.js readfile woes

Node.jsが他の環境と異なる点は、JavaScript実行のシングルスレッドがあるため、コードを実行しているJavaScriptインスタンスが1つしかないことです(アプリのコードを同時に実行する多くのスレッドがあるスレッド環境とは異なります)。時間。)

21
Hector Correa

はい、競合状態(イベントの順序が原因で一貫性のない値を持つ共有リソースの意味で)は、他のコードの実行につながる可能性のある一時停止ポイントがある場所(スレッドで)でも発生しますその任意の行)で、たとえば、完全にシングルスレッドである次の非同期コードを例にとります。

var accountBalance = 0;

async function getAccountBalance() {
    // Suppose this was asynchronously from a database or something
    return accountBalance;
};

async function setAccountBalance(value) {
    // Suppose this was asynchronously from a database or something
    accountBalance = value;
};

async function increment(value, incr) {
    return value + incr;
};

async function add$50() {
    var balance, newBalance;
    balance = await getAccountBalance();
    newBalance = await increment(balance, 50);
    await setAccountBalance(newBalance);
};

async function main() {
    var transaction1, transaction2;
    transaction1 = add$50();
    transaction2 = add$50();
    await transaction1;
    await transaction2;
    console.log('$' + await getAccountBalance());
    // Can print either $50 or $100
    // which it prints is dependent on what order
    // things arrived on the message queue, for this very simple
    // dummy implementation it actually prints $50 because
    // all values are added to the message queue immediately
    // so it actually alternates between the two async functions
};

main();

このコードには待機するたびに一時停止ポイントがあり、予期しない「100ドル」ではなく「50ドル」を生成する2つの関数間でコンテキストスイッチが発生する可能性があります。これは、Wikipediaのスレッドの競合状態の例と基本的に同じですが、一時停止/再入の明示的なポイント。

スレッドと同じように、ロック(別名mutex)のようなものでそのような競合状態を解決できます。したがって、スレッドと同じ方法で上記の競合状態を防ぐことができます。

var accountBalance = 0;

class Lock {
    constructor() {
        this._locked = false;
        this._waiting = [];
    }

    lock() {
        var unlock = () => {
            var nextResolve;
            if (this._waiting.length > 0) {
                nextResolve = this._waiting.pop(0);
                nextResolve(unlock);
            } else {
                this._locked = false;
            }
        };
        if (this._locked) {
            return new Promise((resolve) => {
                this._waiting.Push(resolve);
            });
        } else {
            this._locked = true;
            return new Promise((resolve) => {
                resolve(unlock);
            });
        }
    }
}

var account = new Lock();

 async function getAccountBalance() {
    // Suppose this was asynchronously from a database or something
    return accountBalance;
};

async function setAccountBalance(value) {
    // Suppose this was asynchronously from a database or something
    accountBalance = value;
};

async function increment(value, incr) {
    return value + incr;
};

async function add$50() {
    var unlock, balance, newBalance;

    unlock = await account.lock();

    balance = await getAccountBalance();
    newBalance = await increment(balance, 50);
    await setAccountBalance(newBalance);

    await unlock();
};

async function main() {
    var transaction1, transaction2;
    transaction1 = add$50();
    transaction2 = add$50();
    await transaction1;
    await transaction2;
    console.log('$' + await getAccountBalance()); // Now will always be $100 regardless
};

main();
18
Jamesernator

いいえ、そうです。シングルスレッドの非I/O実行中プログラムで競合状態を発生させることはできません。

ただし、node.jsはプログラミングをブロックしない方法であるため、主に高速です。非ブロッキングとは、リスナーを応答イベントに設定すると、この応答を待っている間に別のことができるということです。

どうして ? 応答を取得するための作業は別のスレッドで行われるためデータベース、ファイルシステム、他のスレッドで実行、クライアントは明らかに別のコンピューターで実行され、ワー​​クフローをプログラムしますその応答に依存することができます。

厳密に言うと、node.jsは1つのスレッドで実行されますが、I/O(データベース、ファイルシステム)、クライアント、およびすべてを含むプログラムワークフローは、多くのスレッドで実行されます。

そのため、データベースに何かを追加する要求を行い、最初の要求の応答を待たずにそれを削除する要求を送信するだけで、競合状態が発生する可能性があります。 データベースがnode.jsと同じスレッドで実行されていて、リクエストがただちに実行される関数呼び出しである場合、競合状態は発生しません。

16
jillro

いいえ。Node.jsには、コンテキストの切り替えによって発生する競合状態はありません。ただし、予期しない順序で非同期イベントが発生して不整合な状態になるnode.jsプログラムを作成することはできます。

たとえば、2つの関数があるとします。最初はWebSocketを介してメッセージを送信し、コールバックで応答を保存します。 2番目の関数は、保存されているすべての返信を削除します。関数を順番に呼び出しても、空のメッセージリストは保証されません。非同期プログラミングを行うときは、可能なすべてのイベントの順序を考慮することが重要です。

編集:ここにいくつかのサンプルコードがあります

var messages = [];

...

io.sockets.on('connection', function (socket) {
    socket.emit('ask', { question: 'How many fish do you have?' });
    socket.on('reply', function (data) {
        messages.Push(data);
    });
    ...
    wipe();
});

function wipe() {
    setTimeout(function() {
        messages = [];
    }, 500);
}
7
aebabis

実際にはスレッドとは何の関係もないので、競合状態が発生する可能性がありますが、イベントのタイミングとシーケンスについての仮定に基づいているため、スレッドはその一例にすぎません。

Node.jsはシングルスレッドですが、それでも並行しており、競合状態が発生する可能性があります。例えば:

var http = require('http');

var size;

http.createServer(function (req, res) {
  size = 0;

  req.on('data', function (data) {
    size += data.length;
  });

  req.on('end', function () {
    res.end(size.toString());
  })

}).listen(1337, '127.0.0.1');

このプログラムは、クライアントに要求のサイズを送信することになっています。テストすると、正しく動作するように見えます。しかし、実際には暗黙の仮定に基づいており、要求の開始イベントと終了イベントの間では何も起こりません。 2つ以上の同時クライアントがある場合は機能しません。

これは、size変数が共有されるために発生します。これは、2つのスレッドが変数を共有する場合とよく似ています。スレッドによく似た抽象的な「非同期コンテキスト」について考えることができますが、一時停止できるのは特定の時点のみです。

7
vkurchatkin

はい。できる。

clusterモジュールを使用して複数のワーカーを初期化すると、Nodejsの競合状態が発生する可能性があります。

ケース

var cluster = require('cluster');
var fs = require('fs');
if(cluster.isMaster){
    for(var i=0;i<4;i++){
        cluster.fork();
    }
}else{
    fs.watch('/path/to/file',function(){
        var anotherFile = '/path/to/anotherFile';
        fs.readFile(anotherFile,function(er,data){
             if(er){
                 throw er;
             }
             data = +data+1;
             fs.writeFile(anotherFile,data,function(er){
                 if(er){
                     throw er;
                 }
                 fs.readFile(anotherFile,function(er,newData){
                     if(er){
                         throw er;
                     }
                     console.log(newData); //newData is now undetermined
                 });
             });
        });
    });
}

監視対象ファイルを変更すると、4人のワーカーが同時にハンドラーを実行します。この動作により、未確定のnewDataが発生します。

ソリューション

if(cluster.isMaster){
    var lock = {};
    var timer = setInterval(function(){
        if(Object.keys(cluster.workers).length >= 4){
            return clearInterval(timer);
        }
        //note that this lock won't 100% work if workers are forked at the same time with loop.
        cluster.fork().on('message',function(id){
             var isLocked = lock[id];
             if(isLocked){
                 return console.log('This task has already been handled');
             }
             lock[id] = 1;
             this.send('No one has done it yet');
        });
    },100);
}else{
     process.on('message',function(){
        //only one worker can execute this task
        fs.watch('/path/to/file',function(){
            var anotherFile = '/path/to/anotherFile';
            fs.readFile(anotherFile,function(er,data){
                 if(er){
                     throw er;
                 }
                 data = +data+1;
                 fs.writeFile(anotherFile,data,function(er){
                     if(er){
                        throw er;
                     }
                     fs.readFile(anotherFile,function(er,newData){
                         if(er){
                             throw er;
                         }
                         console.log(newData); //newData is now determined
                     });
                 });
            });
        });
     });
     //ask the master for permission
     process.send('watch');
}
5
Lewis