Безопасно ли в моем коде гонки обещание NodeJS? - PullRequest
2 голосов
/ 09 апреля 2019

В настоящее время я пишу небольшую многопользовательскую игру в nodejs.

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

Мой способ сделать это следующим образом:

    function playerJoin(){    
        let playerTank = new Tank();
        this.findEmptyArea(playerTank, 100, 100).then((result) => {
            if (!result.success) {
                return;
            }
            addObjectToWorld(playerTank, result.x, result.y);    
        } 
    }


    function findEmptyArea(object, x, y){   
        return new Promise((resolve, reject) => {
          // Psuedo code: code iterates over game objects testing for 
          // collisions . if the requested position is free uses that else 
          // finds an empty nearby location and returns it
          return resolve({success: true, x: freeX, y: freeY});

          // fails to find empty location for object
          return resolve({ success: false });
        } 
  }

Это не фактический код, а урезанная версия, чтобы сделать его более понятным.У меня такой вопрос:

Когда пользователь подключается через веб-сокет, запускается функция playerJoin.затем он создает новый танк игрока, находит свободную область и возвращает обещание, если он был успешным, танк игрока добавляется в мир на позиции.

При внимательном рассмотрении я задавался вопросом, является ли этот кодимеет недостаткиВозможно ли, что addObjectToWorld вызывается в месте, которое на самом деле не является свободным?

Например:

  1. player1 подключает playerJoin, вызванный для player1
  2. findEmptyArea с именемвнутри playerJoin для player1
  3. player 2 подключается,
  4. Обещание findEmptyArea для player2 находит свободное место в 10,10 и возвращает обещание.

  5. Блок кода обещания .then () в playerJoin (после findEmptyArea)) для игрока 1 запускается, ставя игрока на 10,10 с помощью addObjectToWorld

  6. Блок кода обещания .then () в playerJoin для игрока 2 запускается, устанавливая игрока на 10,10 с помощью addObjectToWorld ..и игра вылетает

Так что я предполагаю, что мой вопрос, когда обещание разрешается, сразу же запускается блок кода .then, который запускает addObjectToWorld, или другой код потенциально может выполняться первым (например,другой игрок также нашел область свободной)

Спасибо за вашу помощь

Ответы [ 2 ]

0 голосов
/ 10 апреля 2019

Так что я предполагаю, что мой вопрос, когда обещание разрешится, будет ли немедленно запущен блок кода .then, который запускает addObjectToWorld, или другой код потенциально будет запущен первым (например, другой игрок также найдет область свободной)

Посмотрите этот пример:

'use strict';

async function work(i) {
  const interval = setInterval(() => { console.log('interval', i); }, 1);
  setImmediate(() => console.log('immediate', i));
  process.nextTick(() => console.log('nexttick', i));
  await findEmptyArea(i);
  addObjectToWorld(i, interval);
}

function addObjectToWorld(k, interval) {
  console.log('add object to the world', k);
  clearInterval(interval);
}

function findEmptyArea(k) {
  console.log('findEmptyArea resolving', k);
  return new Promise((resolve, reject) => {
    console.log('findEmptyArea executing', k);
    for (let i = 0; i < 1000000000; i++) {
      // high computation
    }
    resolve({ success: true, x: 1, y: 1 });
  });
}

for (let x = 0; x < 10; x++) {
  work(x);
}

nextTick запускается до addObjectToWorld.

Возможно ли, что цикл событий nodejs разрешит что-то еще запускаться первым?

Если посмотреть на пример, если у вас нет асинхронного кода (I / O), все будет последовательно в зависимости от цикла событий.

Я включил это в обещание, так как playerJoin будет часто запускаться при подключении новых игроков, и я не хочу, чтобы он блокировался.

Учтите, что добавление Promise или async не преобразует ваш код в неблокирование цикла событий: вы все еще блокируете цикл событий также с помощью обещаний (проверьте for < 1000000000: он блокирует цикл обработки событий для всех подключающихся пользователей.

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

Если вам это нравится, ваш цикл обработки событий не остановится, поэтому вы сможете обслуживать больше пользователей и расширять их при необходимости.

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

* +1034 * Пример: * * одна тысяча тридцать пять
'use strict';


const { Worker } = require('worker_threads');

async function work(i) {
  const interval = setInterval(() => { console.log('interval', i); }, 1);
  setImmediate(() => console.log('immediate', i));
  process.nextTick(() => console.log('nexttick', i));
  await findEmptyArea(i);
  addObjectToWorld(i, interval);
}

function addObjectToWorld(k, interval) {
  console.log('add object to the world', k);
  clearInterval(interval);
}

function findEmptyArea(k) {
  console.log('findEmptyArea resolving', k);
  return new Promise((resolve, reject) => {
    const worker = new Worker('./worker.js', { workerData: k });
    worker.on('message', resolve);
    worker.on('error', reject);
    worker.on('exit', (code) => {
      if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`));
    });
  });
}

for (let x = 0; x < 10; x++) {
  work(x);
}

// worker.js

'use strict';

const { workerData, parentPort } = require('worker_threads');

console.log('findEmptyArea executing', workerData);
for (let i = 0; i < 1000000000; i++) {
  // high computation
}
parentPort.postMessage({ hello: workerData });
0 голосов
/ 09 апреля 2019

Поскольку findEmptyArea является асинхронным, он, скорее всего, обращается к чему-то внешнему, верно?

Node.js является однопоточным для выполнения кода JS (даже если в последних версиях есть экспериментальные функции многопоточности).Но Node.JS использует дочерние потоки для доступа к внешним ресурсам.Таким образом, у вас могут быть условия гонки, только если вы обращаетесь к внешним ресурсам, таким как файлы, или делаете запрос API.

Если вы делаете запрос API, доступ к ресурсу отвечает за то, чтобы он возвращал true только один раз, чтобыпредотвратить условия гонки.

Но если вы проверяете только локальные объекты в своих Обещаниях, у вас не должно быть условий гонки (если вы не играете с setTimeout и тому подобными вещами).Цикл событий Node.JS будет выполнять код для каждого разрешенного / отклоненного Promise по одному.Это означает, что после разрешения Обещания выполняется кодовый блок then.

Я нашел эту статью полезной для очереди событий в сочетании с Обещаниями.

ОтветВаш вопрос зависит от того, как findEmptyArea проверяет наличие пустой области.

Несколько советов по кодированию :

Вам не нужно использовать return при вызовеresolve и reject.И вы можете использовать reject, так как вы упомянули переменную:

this.findEmptyArea(playerTank, 100, 100).then((result) => {
  addObjectToWorld(playerTank, result.x, result.y);    
}).catch((err) => {
  // findEmptyArea failed because the Promise was "rejected"
});

return new Promise((resolve, reject) => {
  // On success call this
  resolve({x: freeX, y: freeY});

  // On failure call this
  reject();
});
...