[英]Implementing Lock in Node.js
前言:
為了解決我的問題,你必須具備以下領域的知識:線程安全、Promise、async-await。
對於不熟悉 TypeScript 的人來說,它只是帶有類型注釋的普通 JavaScript (ES6)。
我有一個名為excludeItems
的函數,它接受一個項目列表(每個項目都是一個字符串),並為每個項目調用一個 API(排除項目)。 重要的是不要為同一項目調用 API 兩次,即使在函數的不同執行中也不行,因此我將已排除的項目保存在本地數據庫中。
async function excludeItems(items: string[]) {
var excludedItems = await db.getExcludedItems();
for (var i in items) {
var item = items[i];
var isAlreadyExcluded = excludedItems.find(excludedItem => excludedItem == item);
if (isAlreadyExcluded) {
continue;
}
await someApi.excludeItem(item);
await db.addExcludedItem(item);
}
}
這個函數被它的客戶端瞬間異步調用幾次,這意味着客戶端在第一次執行完成之前調用了這個函數5次。
一個具體的場景:
excludeItems([]);
excludeItems(['A','B','C']);
excludeItems([]);
excludeItems(['A','C']);
excludeItems([]);
在這種情況下,雖然 Node.js 是單線程的,但是這里存在臨界區問題,我得到了錯誤的結果。 這是執行該場景后我在本地數據庫中的“excludedItems”集合:
[{item: 'A'},
{item: 'B'},
{item: 'C'},
{item: 'A'},
{item: 'C'}]
如您所見,最后的“A”和“C”是多余的(這意味着 API 也為這些項目調用了兩次)。
它是由於代碼中的await
語句而發生的。 每次到達等待語句時,都會在底層創建一個新的 Promise,因此盡管 Node.js 是單線程的,但正在等待執行的下一個異步函數正在執行,這樣這個關鍵部分就會並行執行.
為了解決這個問題,我實現了一個鎖定機制:
var excludedItemsLocker = false;
async function safeExcludeItems(items: string[]) {
while (excludedItemsLocker) {
await sleep(100);
}
try {
excludedItemsLocker = true;
var excludedItems: string[] = await db.getExcludedItems();
for (var i in items) {
var item = items[i];
var isAlreadyExcluded = excludedItems.find(excludedItem => excludedItem == item);
if (isAlreadyExcluded) {
continue;
}
await someApi.excludeItem(item);
await db.addExcludedItem(item);
}
}
finally {
excludedItemsLocker = false;
}
}
async function sleep(duration: number): Promise<Object> {
return new Promise(function (resolve) {
setTimeout(resolve, duration);
});
}
但是,由於某種原因,此實現不起作用。 我在關鍵部分仍然得到不止一個(所謂的)“線程”,這意味着它仍然在並行執行,並且我的本地數據庫充滿了相同的錯誤結果。 順便說一句, sleep
方法按預期工作,其目的只是為等待執行的下一個函數調用提供 CPU 時間。
有人看到我的實施中有什么問題嗎?
順便說一句,我知道我可以在不實現 Lock 的情況下實現相同的目標,例如通過在循環內調用db.getExcludedItems
,但我想知道為什么我的 Lock 實現被破壞了。
如果參數是:
['A','B','C']
和db.getExcludedItems()返回:
[{item: 'A'},
{item: 'B'},
{item: 'C'}]
然后你試圖在一個對象數組中找到一個字符串,它總是返回undefined:
var isAlreadyExcluded = excludedItems.find(excludedItem => excludedItem == item);
只是一個想法,因為我看不到鎖定本身的任何問題,它應該按預期工作。
不久前我遇到了類似的問題,我最終實現了一個票證系統,其中每個“線程”都會請求票證並在隊列中等待(我知道它不是一個線程,但它比“下一組函數”<\/em>更容易說)事件循環'<\/em> )。 這是一個在
promise-ticket<\/code><\/a>找到的 NPM 包,但解決方案的關鍵是有一個生成器函數返回 promise,當
EventEmitter<\/code>發出它的票號時,該函數將解析
let emitter = new EventEmitter();
let nextTicket = 0;
let currentTicket = 0;
let skips = [];
const promFn = (resolve) => {
let num = currentTicket++;
emitter.once(num, () => resolve(num));
};
const generator = (function* () {
while(true) yield new Promise(promFn);
})();
// Someone takes a ticket from the machine
this.queue = (resolveValue) => {
let ticketNumber = currentTicket;
let p = generator.next().value;
if(resolveValue !== undefined) p = p.then(() => resolveValue);
if(skips.includes(ticketNumber)) emitter.emit(ticketNumber);
return p;
};
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.