Can node.js code result in race conditions?

后端 未结 6 1794
小鲜肉
小鲜肉 2020-12-02 23:01

From what I read, race conditions occur when different threads try to change a shared variable, which can result in a value that\'s not possible with any serial order of exe

6条回答
  •  失恋的感觉
    2020-12-02 23:45

    Yes, race conditions (in the sense of a shared resource having an inconsistent value due to order of events) can still happen anywhere that there's a point of suspension that could lead to other code being run (with threads its at any line), take for example this piece of async code that is entirely single threaded:

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

    This code has suspension points at every single await and as such could context switch between the two functions at a bad time producing "$50" rather than the expected "$100", this is essentially the same example as Wikipedia's example for Race Conditions in threads but with explicit points of suspension/re-entry.

    Just like threads though you can solve such race conditions with things like a Lock (aka mutex). So we could prevent the above race condition in the same way as threads:

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

提交回复
热议问题