Может ли одновременный доступ на запись к одному и тому же объекту из асинхронных функций вызвать неправильные значения? - PullRequest
0 голосов
/ 17 марта 2020

Интересно, может ли следующий код привести к тому, что целое число i в объекте context будет иметь недопустимое значение. До сих пор я не мог вызвать проблемы в своих экспериментах.

// Node.js (v10.19.0)

const Promise = require('promise')

// object accessed from different async processes in parallel
let context = {i: 0}

// create async functions
let funkMaker = function(i) { 
  return async function(context) { 
    context.i += 1; 
    console.log(`Funk${i} at ${Date.now()}: Incremented argument 'context.i' to: ${context.i}`); 
    return context.i 
  } 
}

// create async processes
let funksArr = []; 
for (k=1; k<101; k++) { 
  funksArr.push(funkMaker(k)) 
}

// invoke async processes in parallel
Promise.all(
  funksArr.map(f => f(context))
).then(
  x => console.log(`Promise.all resolved with ${x}`)
).catch(
  e => console.log(`Promise.all raised an error:\n${e}`)
)

Выше приведен следующий вывод (отрывок):

Funk1 at 1584448782621: Incremented argument 'context.i' to: 1   
Funk2 at 1584448782621: Incremented argument 'context.i' to: 2   
Funk3 at 1584448782622: Incremented argument 'context.i' to: 3   
Funk4 at 1584448782622: Incremented argument 'context.i' to: 4   
Funk5 at 1584448782622: Incremented argument 'context.i' to: 5   
Funk6 at 1584448782622: Incremented argument 'context.i' to: 6   
Funk7 at 1584448782622: Incremented argument 'context.i' to: 7    
Funk8 at 1584448782622: Incremented argument 'context.i' to: 8  
Funk9 at 1584448782622: Incremented argument 'context.i' to: 9

Как вы можете видеть, есть несколько процессов, которые похоже, что он обращается к context.i в ту же миллисекунду (Funk3 до Funk9 выше). Я озадачен тем, почему целое число context.i все еще увеличивается правильно и без ошибок.

Выводы приветствуются. Спасибо!

ОБНОВЛЕНИЕ

Чтобы попробовать это в реальных системных темах, я исправил код, украденный из этого сообщения в блоге , который использует crypto и неявно libuv для выполнения в реальных потоках. Я все еще не мог заставить мой context.i сломаться. И я все еще озадачен, если честно.

const crypto = require("crypto");
const start = Date.now();

let context = {i: 0}

function logHashTime(context) {
  crypto.pbkdf2("a", "b", 100000, 512, "sha512", () => {
    context.i += 1;
    console.log(`Hash: ${Date.now() - start}, 'context.i' incremented to: ${context.i}`);
  });
}

for (i = 0; i < 100; i++) {
  logHashTime(context)
}

Вывод (выдержка) все еще "в порядке":

Hash: 1268, 'context.i' incremented to: 1  
Hash: 1460, 'context.i' incremented to: 2  
Hash: 1660, 'context.i' incremented to: 3  
Hash: 1907, 'context.i' incremented to: 4  
Hash: 2493, 'context.i' incremented to: 5  
Hash: 2673, 'context.i' incremented to: 6  
Hash: 3154, 'context.i' incremented to: 7  
Hash: 3215, 'context.i' incremented to: 8  
Hash: 3662, 'context.i' incremented to: 9

1 Ответ

2 голосов
/ 17 марта 2020

Во-первых, у вас нет действительного асинхронного кода здесь. Хотя ваша функция объявлена ​​async, ее тело все еще выполняется синхронно, и в этом теле нет никаких реальных асинхронных операций. Тот факт, что он помечен как async, означает, что он возвращает обещание, но это все, что изменяется в вашем примере.

Во-вторых, Javascript запускает ваш Javascript в одном потоке, если вы явно не используете Worker Потоки (которые вы там не используете).

Итак, поскольку все синхронно и все выполняется в одном потоке, код просто надежно выполняется в порядке, в котором работает .map() l oop. Единственное, что здесь действительно асинхронно, - это когда обещания, возвращаемые функцией async, вызывают их обработчик .then(), который позволяет Promise.all() вызывать ее обработчик .then(). Но все результаты были сгенерированы синхронно до этого.

Как вы можете видеть, есть несколько процессов, которые, по-видимому, обращаются к context.i в одну и ту же миллисекунду (Funk3 to Funk9 выше). Я озадачен тем, почему целочисленный context.i по-прежнему увеличивается правильно и без ошибок.

Все они запускаются по одному по порядку. Вы видите одну и ту же миллисекунду только потому, что у вас недостаточно разрешения таймера, чтобы увидеть разницу во времени выполнения. Ни одна из функций на самом деле не работает "в одно и то же время".

context.i правильно увеличивается, потому что функции на самом деле вызываются и выполняются одна за другой по порядку.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...