Что представляет собой типичный класс проблем, вызываемых изменчивыми типами данных в однопоточной среде? - PullRequest
2 голосов
/ 04 ноября 2019

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

const m = new Map([["foo", true]]);

//...

m.set("bar", false);

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

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

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

Заранее спасибо!

Ответы [ 3 ]

3 голосов
/ 05 ноября 2019

JS моделирует параллелизм в цикле событий. В результате нет условий гонки.

Давайте остановимся прямо здесь. Вы можете не получить два разных потока , которые пытаются получить доступ к одной и той же ячейке памяти в одно и то же время, но у вас могут быть одновременные части вашей программы, которые обращаются к изменяемому состоянию асинхронно и игнорируют тот факт, что они не одиноки. Это все еще состояние гонки.

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

var clock = out.value = 0;

async function incrementSlowly() {
  if (clock == 12)
    clock = 0; // reset
  await delay(1000);
  clock++;
  out.value = clock;
}
function delay(t) { return new Promise(resolve => setTimeout(resolve, t)); }
<output id="out"></output>
<button onclick="incrementSlowly()">Tick!</button>

Значение clock никогда не будет больше 12? Попробуйте сами, что происходит, когда вы быстро нажимаете на кнопку.

Несколько вызовов функции incrementSlowly выполняются независимо и выполняют проверку в неправильное время - во время задержки другой экземпляр мог уже увеличитьсяснова clock.

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

Использование неизменяемых структур данных вынуждает вас делать операции с состоянием явными, и стало бы ясно, что incrementSlowly фактически обращается к состояниюв два раза.

1 голос
/ 05 ноября 2019

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

Это не совсем исчерпывающе, вы также можете получить параллелизм в javascript, запустив вашу программу на нескольких дочерних процессах , и в этом случае несколько потоков могут изменять одну ссылку на памятьдействительно может привести к гоночным условиям или тупикам. И да, неизменность - это один из шаблонов проектирования, принятых для обеспечения поточной безопасности : [в основном] путем принудительного предоставления общих данных только для чтения .

ЭтоХорошая статья , объясняющая, почему и как вы можете столкнуться с условиями гонки в многопоточной среде, такой как Java.


Вы правы, нет ничего плохого в мутировании ссылок на память воднопоточные языки, на самом деле, так было сделано в течение очень долгого времени в javascript. Неизменность набрала обороты только недавно. Гилель Уэйн также объясняет, как удаление параллелизма полностью помогает снять боль, вызванную изменчивостью.

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

Легко понять, подумав с архитектурой, как изменчивость приводит к непредсказуемому программному обеспечению. Гарантирует ли что-либо, что при определенных условиях объект будет находиться в определенном состоянии? На самом деле, нет. Сколько объектов может вызвать изменение состояния данного объекта? Могут ли эти изменения быть под контролем? Не совсем, думать о переменной в основной области видимости ... буквально все может повлиять на ее состояние, и предположения типа " каждая операция, которую следует учитывать [...] " небезопасны и очень подвержены ошибкам.

Итак, хотя изменчивость не обязательно является неправильной, Неизменяемость - это еще один инструмент в наборе инструментов вашего разработчика , и его освоение делает вас лучшим разработчиком.

Неизменяемая структура данных (неизменяемый объект)поддерживает только одну операцию: чтение, и это приводит к тому, что ваша программа ведет себя как машина Мура: , учитывая конкретный момент времени, ваша программа всегда будет в одном из возможных состояний .

ВашПрограмма - это конвейер операций, который всегда можно рассчитать и измерить:

R.pipe(
  R.toUpper, // You know the state of your program here
  R.concat(R.__, '!!'), // or here
)('Hello World'); // or here

Вы также можете поменять местами один из этапов с его возвращаемым значением и при этом ваша программа будет вести себя как положено:

R.pipe(
  R.always('HELLO WORLD'),
  R.concat(R.__, '!!'), 
)('Hello World');

Неизменность также позволяет путешествовать во времени и делает тестирование очень простым, но что действительно важно, так это то, что делаеточень просто рассуждать о состояниях и их переходах только потому, что вы рассматриваете каждое значение как примитив: user.set('name', 'Giuseppe') становится не отличным от 'Giuseppe'.toUpperCase().

Ваша программа в конечном итоге представляет собой четко определенную сериюмоментальные снимки:

-> 'Hello World' -> 'HELLO WORLD' -> 'HELLO WORLD!!'
t(0) ----------- t(1) ----------- t(2) ------------- t(n)

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

const user = { name: 'Giuseppe' };
const equals = (given, expected) => given === expected;

const newUser = { ...user, name: 'Marco' };

console.log('are users equals:', equals(user, newUser));

Вам потребуется функция deepEqual для получения того же результата с изменчивостью ... (подробнее на веб-сайте redux )

1 голос
/ 04 ноября 2019

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

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

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

Однажды во время обзора кода я указал, что эта функция изменяет свой параметр:(примерно это выглядело так)

function foo(bar) {
  const baz = {...bar};
  baz.a.b.c = 10;
  return baz;
}

На что автор функции ответил: «Нет, я ее клонировал раньше. Так что функция« чистая »».

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

Для меня это самое худшее, что может случиться при мутировании данных: путаница.

Ошибки, вызванные мутацией, сложно отследить.

Я всегда призываю людей в моей команде не кодировать «невозможный случай», потому что это часто приводит к тому, что код загроможден «на всякий случай». «проверяет, что увеличивает усилия по сопровождению и подрывает нашу уверенность в коде.

Однако« невозможный случай »просто ждет вас за углом, если вы разрешите неконтролируемый доступ к вашим данным.

Я уже продемонстрировал, что люди видоизменяют вещи, не осознавая этого. Когда в вашей команде есть люди с разным уровнем опыта, лучшее, что вы можете сделать, это:

  1. Никогда не принимайте что-либо
  2. Научите их
  3. Используйте библиотекудля обеспечения неизменности

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

...