1

Предисловие:Реализация блокировки в Node.js

Для того, чтобы решить мою проблему, вы должны иметь знания в следующих областях: безопасность потоков, Promise, асинхронной-ОЖИДАНИЕ.

Для людей, которые не знакомы с 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 является однопоточным, выполняется следующая функция async, ожидающая выполнения, и таким образом этот критический раздел выполняется параллельно ,

Чтобы решить эту проблему, я реализовал механизм блокировки:

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

Однако эта реализация не работает по какой-то причине. Я все еще получаю более одного (предполагаемого) «потока» в критическом разделе, что означает, что он все равно выполняется параллельно, а локальная БД заполняется теми же неправильными результатами. BTW, метод sleep работает так, как ожидалось, его целью является просто дать процессорное время следующему вызову функции, который ожидает исполнения.

Кто-нибудь видит, что сломалось в моей реализации?

BTW Я знаю, что могу достичь той же цели без реализации блокировки, например, позвонив в db.getExcludedItems внутри цикла, но я хочу знать, почему нарушена реализация блокировки.

ответ

0

Если параметры:

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

и дб.getExcludedItems() возвращает:

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

Затем вы пытаетесь найти строку в массив объектов, который всегда будет возвращать неопределенными:

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

Просто мысли, потому что я не вижу какой-либо проблема с самой блокировкой, она должна работать должным образом.

+0

Прошу прощения. Я забыл сказать, что db.getExcludedItems() возвращает массив строк. Однако спасибо за помощь. – Alon

+0

В любом случае я видел в отладочном времени, что два (якобы) «потока» одновременно входят в критический раздел. – Alon