[英]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.