簡體   English   中英

"在 Node.js 中實現鎖"

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

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