简体   繁体   English

"在 Node.js 中实现锁"

[英]Implementing Lock in Node.js

Preface:前言:

In order to solve my problem, you have to have knowledge in the following areas: thread-safety, Promise, async-await.为了解决我的问题,你必须具备以下领域的知识:线程安全、Promise、async-await。

For people who are not familiar with TypeScript, it's just normal JavaScript (ES6) with type annotations.对于不熟悉 TypeScript 的人来说,它只是带有类型注释的普通 JavaScript (ES6)。


I have a function named excludeItems that accepts an item list (each item is a string), and calls an API (that excludes the item) for each item.我有一个名为excludeItems的函数,它接受一个项目列表(每个项目都是一个字符串),并为每个项目调用一个 API(排除项目)。 It's important not to call the API twice for the same item, not even in different executions of the function, so I save in a local DB the items that are already excluded.重要的是不要为同一项目调用 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);
            }
     }

This function is called asynchronously by its client several times instantaneously, meaning the client calls the function say 5 times before the first execution is completed.这个函数被它的客户端瞬间异步调用几次,这意味着客户端在第一次执行完成之前调用了这个函数5次。

A concrete scenario:一个具体的场景:

excludeItems([]);
excludeItems(['A','B','C']);
excludeItems([]);
excludeItems(['A','C']);
excludeItems([]);

In this case, although Node.js is single-threaded, the Critical Section problem is existing here, and I get the wrong results.在这种情况下,虽然 Node.js 是单线程的,但是这里存在临界区问题,我得到了错误的结果。 This is my "excludedItems" collection in my local DB after the execution of that scenario:这是执行该场景后我在本地数据库中的“excludedItems”集合:

[{item: 'A'},
{item: 'B'},
{item: 'C'},
{item: 'A'},
{item: 'C'}]

As you can see, the last 'A' and 'C' are redundant (meaning that the API was also called twice for these items).如您所见,最后的“A”和“C”是多余的(这意味着 API 也为这些项目调用了两次)。

It occurs due to the await statements in the code.它是由于代码中的await语句而发生的。 Every time an await statement is reached, a new Promise is created under the hood, therefore although Node.js is single-threaded, the next async function that was waiting to be executed is getting executed, and that way this critical section is executed parallelly.每次到达等待语句时,都会在底层创建一个新的 Promise,因此尽管 Node.js 是单线程的,但正在等待执行的下一个异步函数正在执行,这样这个关键部分就会并行执行.

To solve that problem, I've implemented a locking mechanism:为了解决这个问题,我实现了一个锁定机制:

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

However, this implementation does not work for some reason.但是,由于某种原因,此实现不起作用。 I still get more than one (alleged) "thread" in the critical section, meaning it's still getting executed parallelly and my local DB is filled with the same wrong results.我在关键部分仍然得到不止一个(所谓的)“线程”,这意味着它仍然在并行执行,并且我的本地数据库充满了相同的错误结果。 BTW the sleep method works as expected, its purpose is just to give CPU time to the next function call that's waiting to be executed.顺便说一句, sleep方法按预期工作,其目的只是为等待执行的下一个函数调用提供 CPU 时间。

Does anybody see what's broken in my implementation?有人看到我的实施中有什么问题吗?

BTW I know that I can achieve the same goal without implementing a Lock, for example by calling to db.getExcludedItems inside the loop, but I want to know why my Lock implementation is broken.顺便说一句,我知道我可以在不实现 Lock 的情况下实现相同的目标,例如通过在循环内调用db.getExcludedItems ,但我想知道为什么我的 Lock 实现被破坏了。

If the parameters are: 如果参数是:

['A','B','C']

and db.getExcludedItems() returns: 和db.getExcludedItems()返回:

[{item: 'A'},
{item: 'B'},
{item: 'C'}]

Then you are trying to find a string in an array of objects, which will always return undefined: 然后你试图在一个对象数组中找到一个字符串,它总是返回undefined:

var isAlreadyExcluded = excludedItems.find(excludedItem => excludedItem == item);

Just a thought, because I can't see any problem with the locking itself, it should work as expected. 只是一个想法,因为我看不到锁定本身的任何问题,它应该按预期工作。

I encountered a similar problem a while back and I ended up implementing a ticket system where each "thread" would request a ticket and wait in a queue (I know it's not a thread, but it's easier to say than 'next set of functions in the event loop'<\/em> ).不久前我遇到了类似的问题,我最终实现了一个票证系统,其中每个“线程”都会请求票证并在队列中等待(我知道它不是一个线程,但它比“下一组函数”<\/em>更容易说)事件循环'<\/em> )。 It's an NPM package found at promise-ticket<\/code><\/a> , but the crux of the solution was to have a generator function returning promises that would resolve when an EventEmitter<\/code> would emit it's ticket number这是一个在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