簡體   English   中英

Node.js:如何實現簡單實用的互斥機制以避免在同步操作中繞過保護語句的競爭條件

[英]Node.js: How to implement a simple and functional Mutex mechanism to avoid racing conditions that bypass the guard statement in simultaneous actions

在下面的class中, _busy字段作為一個信號量 但是,在“同時”的情況下它無法守衛!

class Task {
    _busy = false;

    async run(s) {
        try {
            if (this._busy)
                return;
            this._busy = true;
            await payload();
        } finally {
            this._busy = false;
        }
    }
}

run()的唯一目的是專門執行payload()在它仍在進行時拒絕所有其他調用 換句話說,當“任何”調用到達run()方法時,我希望它只允許第一個調用 go 通過並鎖定它(拒絕所有其他調用)直到它完成其有效負載; “最后”,一旦完成,它就會打開。

在上面的實現中,通過應用程序的各個部分同時調用run()方法確實會出現競速條件。 一些調用(超過 1 個)使其通過“保護” if語句,因為它們還沒有到達this._busy = true以鎖定它(它們同時通過)。 所以,目前的實施並沒有削減它!

我只想在其中一個已經執行時拒絕同時調用。 我正在尋找一個簡單的解決方案來解決這個問題。 我已將異步互斥庫指定為最后的手段!

那么,如何實現一個簡單的“鎖定”機制來避免在同步操作中繞過 guard 語句的競爭條件呢?


為了更清楚地說明,根據下面的評論,以下幾乎是實際Task class(沒有無關緊要)。

class Task {
    _cb;
    _busy = false;
    _count = 0;

    constructor(cb) {
        this._cb = cb;
    }

    async run(params = []) {
        try {
            if (this._busy)
                return;
            this._busy = true;
            this._count++;
            if (this._count > 1) {
                console.log('Race condition!', 'count:', this._count);
                this._count--;
                return;
            }
            await this._cb(...params);
        } catch (err) {
            await someLoggingRoutine();
        } finally {
            this._busy = false;
            this._count--;
        }
    }
}

我確實遇到了Race condition! 日志。 此外,所有任務實例對於一個簡單的驅動程序文件都是本地的(這些實例不會傳遞給任何其他 function,它們僅作為本地實例在單個 function 中徘徊。)它們以以下形式創建:

const t1 = new Task(async () => { await doSth1(); /*...*/ });
const t2 = new Task(async () => { await doSth2(); /*...*/ });
const t3 = new Task(async () => { await doSth3(); /*...*/ });
// ...

我確實在各種庫事件中調用它們,其中一些事件同時發生並導致“競爭條件”問題; 例如:

someLib.on('some-event', async function() { /*...*/ t1.run().then(); /*...*/ });
anotherLib.on('event-2', async function() { /*...*/ t1.run().then(); /*...*/ });

天哪,現在我明白了。 我怎么會錯過這么久:這是你的實現:

async run() {
   try {
      if (this._busy)
         return;
      ...
   } finally {
      this._busy = false;
   }
}

根據文檔

finally塊中的語句在控制流退出try...catch...finally構造之前執行。 無論異常是被拋出還是被捕獲,這些語句都會執行。

因此,當它很忙並且流到達保護if時,然后在邏輯上遇到return語句。 return語句導致流程退出try...catch...finally構造; 因此,根據文檔,無論如何執行finally塊中的語句:設置this._busy = false; ,打開東西!


因此,第一次調用run()this._busy設置為true 然后進入關鍵部分及其長時間運行的回調。 當此回調運行時,另一個事件會導致調用run() 第二次調用被 guarding if語句合理地阻止進入臨界區:

if (this._busy) return;

遇到return語句退出 function(從而退出try...catch...finally構造)導致執行finally塊中的語句; 因此, this._busy = false重置標志,即使第一個回調仍在運行! 現在假設調用了另一個事件對run()的第三次調用! 由於this._busy剛剛被設置為false ,流程愉快地再次進入臨界區,即使第一個回調仍在運行,反過來,它將this._busy設置回true 同時,第一個回調完成,到達finally塊,再次設置this._busy = false 即使另一個回調仍在運行。 所以下一次調用run()可以毫無問題地再次進入臨界區……等等……

所以要解決這個問題,關鍵部分的檢查應該在try之外

async run() {
   if (this._busy) return;
   this._busy = true;

   try { ... }
   finally {
      this._busy = false; 
   }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM