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

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

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

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

ベストアンサー1

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

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();

このコードには、すべての await に一時停止ポイントがあるため、2 つの関数間でコンテキストが不適切なタイミングで切り替わり、期待される「$100」ではなく「$50」が生成される可能性があります。これは、スレッドでの競合状態に関する 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();

おすすめ記事