在Node.js中实现Lock

时间:2017-02-12 13:41:02

标签: javascript node.js multithreading synchronization 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语句而发生。每次达到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);
    });
}

但是,此实现由于某种原因不起作用。我仍然得到不止一个(涉嫌)&#34;线程&#34;在关键部分,这意味着它仍然会并行执行,而我的本地数据库也会出现相同的错误结果。 BTW sleep方法按预期工作,其目的只是为下一个等待执行的函数调用提供CPU时间。

有人看到我的实施中发现了什么?

BTW我知道我可以在不实现Lock的情况下实现相同的目标,例如通过在循环内调用db.getExcludedItems,但我想知道为什么我的Lock实现被破坏了。

1 个答案:

答案 0 :(得分:0)

如果参数是:

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

和db.getExcludedItems()返回:

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

然后你试图在一个对象数组中找到一个字符串,它总是返回undefined:

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

只是一个想法,因为我看不到锁定本身的任何问题,它应该按预期工作。