Здравствуйте, Sharov, Вы писали:
H>>Это интересно. А как там с многопоточностью, в движке ноды? Как разграничивается доступ к общему ресурсу из нескольких потоков?
S>Он однопоточных и асинхронный. Доступ по идее будет сериализуемым.
Это фатальное заблуждение. В ноде никакого серилизуемого доступа, если речь не про блокирующие вызовы. Он однопоточный, но тем не менее многозадачный. Те самые колбеки и тд, это кооперативная многозадачность. То есть, недетерминизм в полный рост. Только гонки надо искать не из за потоков, а из за колбеков и эвентлупа.
Например, операция доступа к ресурсу следующая —
async update(path, pattern) {
const resource = await open(path); // доступ к ресурсу разделяемый, а не эксклюзивный !!!
const oldHeader = await readHeader(resource);
const {newPattern, newHeader} = await nextStep(pattern, oldHeader);
await writeToEnd(resource, newPattern);
await updateHeader(resource, newHeader);
await close(resource);
}
В итоге, ресурс хранит данные всех шагов некоторого вычисления и в хидере указаны соответствующие ссылки.
Теперь представим, что будет, если сделаем, скажем, вот такую хрень
То есть, мы здесь запустили 6 параллельных цепочек, каждая из которых работает с одним и тем же разделяемым ресурсом.
Вопрос — что будет в конце? А будет хаос, содержимое файла скорее всего будет представлять непойми что — одна цепочка обновила хидер, этот хидер тут же перезаписывается другой цепочкой. Она цепочка сделала запись в конец, но по этому же смещению тут же пишет другая цепочки.
Вот это сериализуемый доступ, и сделано это руками. То есть, такую гарантию, как сериализуемый доступ, в ноде приходится делать руками. И так с любым разделяемым ресурсом.
Реально вот такое чудо не встречается, что бы все опреации сами по себе шли подряд, нужно пилить чтото более приемлемое. Например, соорудить нечто навроде мутекса или семафора:
const mutex = queue(1); // 1 - количество читателей-писателей которым разрешено одновременно работать с ресурсом
// http хандлер
app.post(async (req, res) => {
const {pattern} = resolveArgs(req);
await mutex(() => update(path, pattern)); // все запросы автоматически становятся в очередь
res.send('', 204);
});
I>В итоге, ресурс хранит данные всех шагов некоторого вычисления и в хидере указаны соответствующие ссылки. I>Теперь представим, что будет, если сделаем, скажем, вот такую хрень
I>
I>То есть, мы здесь запустили 6 параллельных цепочек, каждая из которых работает с одним и тем же разделяемым ресурсом.
Разве это параллельное исполнение? Это а-ля корутины для эмуляции многопоточности, т.е. в один момент времени с файлом будет работать только один
update(path, patternX), но мы правда не знаем какой...
Здравствуйте, Lazytech, Вы писали:
L>На днях, делая свое первое приложение на Node.js, был приятно удивлен простотой использования CommonJS (особенно если применять деструктуризацию). А в свежую версию Node вроде добавили поддержку ECMAScript Modules (пока experimental), с которыми работать еще удобнее.
Да, у этих модулей тоже зоопарк.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, Sharov, Вы писали:
I>>То есть, мы здесь запустили 6 параллельных цепочек, каждая из которых работает с одним и тем же разделяемым ресурсом.
S>Разве это параллельное исполнение? Это а-ля корутины для эмуляции многопоточности,
Это именно параллельное исполнение. В каком порядке выполняться будут операции и их части — никому неизвестно.
async/await это не эмуляция многопоточности, это связывание кусочков одной цепочки вместе. Унутре принципиально те же колбеки.
S>т.е. в один момент времени с файлом будет работать только один update(path, patternX), но мы правда не знаем какой...
async/await это принципиально тот же коллбек. Что бы гарантировать, что с ресурсом будет работать ровно один update, нужен механизм для линеаризации, та самая queue из моего примера.
Если логическая цепочка одна — то её части выполняются последовательно, для этого и нужен async/await, это связывание. Ровно так же, как и с колбеком-продолжением.
А вот если цепочек несколько, то никаких гарантий тебе никто не даст.
Вот решение задачи — просто инкрементим содержимое файла. Попробуй поиграться, запуская от 1 до n задач.
I>Вот это сериализуемый доступ, и сделано это руками. То есть, такую гарантию, как сериализуемый доступ, в ноде приходится делать руками. И так с любым разделяемым ресурсом.
А если из "await update(path, pattern3)" полетит exception, то будет невнятное "Unhandled blabla..." без нормального стека и отладочным адом. Там же надо .catch ещё прописать, если я ничего не путаю. Или уже придумали нового сахара?
I>Реально вот такое чудо не встречается, что бы все опреации сами по себе шли подряд, нужно пилить чтото более приемлемое. Например, соорудить нечто навроде мутекса или семафора:
Грозились же в ноде сделать мьютексы и потоки.
I>
I>const mutex = queue(1); // 1 - количество читателей-писателей которым разрешено одновременно работать с ресурсом
I>// http хандлер
I>app.post(async (req, res) => {
I> const {pattern} = resolveArgs(req);
I> await mutex(() => update(path, pattern)); // все запросы автоматически становятся в очередь
I> res.send('', 204);
I>});
I>
Опять же с обработкой ошибок не понятно, и внутренний голос говорит что однажды всё зависнет в строчке "await mutex(() => update(path, pattern))" из-за хрен пойми чего.
Как только нужно будет всё сделать в продуктовом качестве, так сразу всё обрастёт соплями и затычками. Нужно очень хорошее понимание принципов работы всего под капотом ноды, знание всех бест практикс и развитый мозг. Где взять таких программистов? Это рушит концепцию быстрого тайм ту маркет, одного языка для бэкенда и фронта. Не получается дёшево и эффективно, как это нередко утверждается про nodejs.
Здравствуйте, Maniacal, Вы писали:
M>Один опытный JavaScript-программист ещё во времена DHTML 4.0 мне тогда ещё неопытному в 1998 году говорил, что на JS можно сделать всё. И это реально правда даже по меркам того времени.
Это для любого тьюринг-полного языка правда.
Переубедить Вас, к сожалению, мне не удастся, поэтому сразу перейду к оскорблениям.
Здравствуйте, TimurSPB, Вы писали:
I>>Вот это сериализуемый доступ, и сделано это руками. То есть, такую гарантию, как сериализуемый доступ, в ноде приходится делать руками. И так с любым разделяемым ресурсом. TSP>А если из "await update(path, pattern3)" полетит exception, то будет невнятное "Unhandled blabla..." без нормального стека и отладочным адом. Там же надо .catch ещё прописать, если я ничего не путаю. Или уже придумали нового сахара?
Обычный сахар — try...catch, и не надо ничего придумывать.
I>>Реально вот такое чудо не встречается, что бы все опреации сами по себе шли подряд, нужно пилить чтото более приемлемое. Например, соорудить нечто навроде мутекса или семафора: TSP>Грозились же в ноде сделать мьютексы и потоки.
При чем здесь это? Увидел слово мутекс и решил вспомнить потоки? Мутекс нужен в любой многозадачности, даже однопоточной, как ноде.
Да, в ноде многозадачность на одном потоке.
TSP>Опять же с обработкой ошибок не понятно, и внутренний голос говорит что однажды всё зависнет в строчке "await mutex(() => update(path, pattern))" из-за хрен пойми чего.
Однажды у тебя и в потоке может случиться бесконечный цикл или дедлок. Что с того?
Async/await как и промисы, это обычные колбеки унутре, никакой магии.
Потерял маленький кусочек и никогда не дождешься окончания задачи. Чем это отличается от кода в дотнете на асинк-авейтах? Ничем. Потерял — недождешься.
TSP>Как только нужно будет всё сделать в продуктовом качестве, так сразу всё обрастёт соплями и затычками.
У меня ничего не обрастает и работает как положено. Собственно, у большинства примерно так же. Обрастает соплями и затычками в любом стеке, хоть дотнет, хоть нод, хоть джава, по самым разным причинам — в основном из за особого отношения к работе "и так сойдет".
Здравствуйте, Ops, Вы писали:
Ops>Здравствуйте, Maniacal, Вы писали:
M>>Один опытный JavaScript-программист ещё во времена DHTML 4.0 мне тогда ещё неопытному в 1998 году говорил, что на JS можно сделать всё. И это реально правда даже по меркам того времени.
Ops>Это для любого тьюринг-полного языка правда.
Он имел в виду связку Винда+IE в те времена. Полноценный GUI со всякими прогрессбарами, статусбарами, листбоксами, выпадающими меню и другими интерактивными самописными элементами (DHTML), 2D и 3D анимация (DirectAnimation), работа со звуком, джойстиком (DirectInput), работа с сетью (DirectPlay). Про работу с "БД" тоже что-то было написано в учебнике (с файлами на стороне сервера).
Тогда непонятно будет откуда оно полетело.
I>У меня ничего не обрастает и работает как положено. Собственно, у большинства примерно так же. Обрастает соплями и затычками в любом стеке, хоть дотнет, хоть нод, хоть джава, по самым разным причинам — в основном из за особого отношения к работе "и так сойдет".
У тебя будет, но сколько ты стоишь и где найти команду таких как ты? В суровой реальности с этим проблемы.
Здравствуйте, TimurSPB, Вы писали:
TSP>Тогда непонятно будет откуда оно полетело.
Бывает.
I>>У меня ничего не обрастает и работает как положено. Собственно, у большинства примерно так же. Обрастает соплями и затычками в любом стеке, хоть дотнет, хоть нод, хоть джава, по самым разным причинам — в основном из за особого отношения к работе "и так сойдет". TSP>У тебя будет, но сколько ты стоишь и где найти команду таких как ты? В суровой реальности с этим проблемы.
Проблема аналогична тем, что в других стеках. Единственно, где сливает именно жээс, это числодробилки. Это особенность такой его многозадачности.
Здравствуйте, Ikemefula, Вы писали:
I>Это именно параллельное исполнение. В каком порядке выполняться будут операции и их части — никому неизвестно.
пожалуйста изучите термины, прежде чем их использовать. Это не параллельное исполнение, это асинхронное исполнение continuation'ов (в случае с js — генераторов, в других языках, как уже упомянули выше — это могли бы быть корутины). Параллельное исполнение подразумевает два+ вычислителя(две+ машины тьюринга — два+ процессора, две+ виртуальные машины, два+ стека), работающих одновременно. В node.js исполнительный поток один (хотя есть и воркеры). Грубо говоря у вас например никогда не будет гонки при инкременте глобального счётчика, т.к. параллельно код инкремента исполнится не может в принципе.
Здравствуйте, Мирный герцог, Вы писали:
МГ>Здравствуйте, Ikemefula, Вы писали:
I>>Это именно параллельное исполнение. В каком порядке выполняться будут операции и их части — никому неизвестно.
МГ>пожалуйста изучите термины, прежде чем их использовать. Это не параллельное исполнение, это асинхронное исполнение continuation'ов (в случае с js — генераторов, в других языках, как уже упомянули выше — это могли бы быть корутины).
Здравствуйте, Мирный герцог, Вы писали:
МГ>пожалуйста изучите термины, прежде чем их использовать. Это не параллельное исполнение, это асинхронное исполнение continuation'ов (в случае с js — генераторов, в других языках, как уже упомянули выше — это могли бы быть корутины). Параллельное исполнение подразумевает два+ вычислителя(две+ машины тьюринга — два+ процессора, две+ виртуальные машины, два+ стека), работающих одновременно. В node.js исполнительный поток один (хотя есть и воркеры). Грубо говоря у вас например никогда не будет гонки при инкременте глобального счётчика, т.к. параллельно код инкремента исполнится не может в принципе.
Ты путаешь логическое и физическое выполнение. Физическое как раз интересует меньше всего, т.к. ты никогда не знаешь, сколько у тебя физических вычислителей. Все что ты можешь сказать, что их больше нуля. Раз код выполняется, есть минимум 1
Соответственно, у меня в коде показаны логически 6 параллельных задач.
Гонки это свойство многозадачности, а не только потоков. Абсолютно любая многозадачность имеет гонки в соответствии со своей природой.
Гонки при инкременте глобального счетчика — раскрой глаза, я именно этот вариант показал. Все определяет протокол доступа к ресурсу. Если операция над ресурсом в рамках конкретной многозадачности неатомарна, то она будут давать те самые гонки если запустить параллельно несколько таких операций.
Здравствуйте, Reset, Вы писали:
R>Асинхронные фреймворки есть почти на всем, но в NodeJS асинхронщина изначально закладывалась как обязательная возможность.
Нет. жс тупо по другому не умеет. Приходится изворачиваться.
Здравствуйте, Ikemefula, Вы писали:
I>Ты путаешь логическое и физическое выполнение.
нет, я ничего не путаю, я вообще не говорил о "логическом" и "физическом" выполнении, особенно странно слышать о "физическом" выполнении когда я явно упомянул виртуальную машину как вычислитель.
I>Гонки это свойство многозадачности, а не только потоков. Абсолютно любая многозадачность имеет гонки в соответствии со своей природой.
я смотрю ты не понимаешь что такое гонки. Гонки, это когда у тебя выполняется 10 инкрементов, а в результате переменная инкрементируется на 9, или вообще на 1, вот это гонки. А то что ты называешь "гонками" это обычное асинхронное исполнение. Оно по определению не упорядоченно, но это не гонки и не "параллелизм".
I>Гонки при инкременте глобального счетчика — раскрой глаза, я именно этот вариант показал.
Я ещё раз говорю, давай код в котором в ноде будет гонка с инкрементом счётчика. То что ты можешь растекаться по древу мыслями используя собственное понимание общепринятой терминологию я уже понял.
Здравствуйте, Мирный герцог, Вы писали:
МГ>Здравствуйте, Ikemefula, Вы писали:
I>>Ты путаешь логическое и физическое выполнение. МГ>нет, я ничего не путаю, я вообще не говорил о "логическом" и "физическом" выполнении, особенно странно слышать о "физическом" выполнении когда я явно упомянул виртуальную машину как вычислитель.
В том то и дело, что не говорил, а надо бы Логически в моем примере целых 6 задач выполняются параллельно. Физически — однопоточный интерпретатор управляет состоянием и работой многопоточной виртуальной машины.
Из букваря по Node.js — физически отдельные части операций или даже операции целиком выполняются на отдельных вычислителях, ядрах, процессорах или потоках. Например, чтение из файла может выполняться буквально одновременно — два разных потока вычитывают все что надо, а по окончании готовый результат прокидывают в интерпретатор. Возможности параллелизма ограничены в данном случае не количеством ядер-процессоров, а возможностями системной шины.
Даже если под капотом нода ничего не будет(гипотетически), все равно есть операционка, и она точно может делать несколько дисковых операций одновременно.
Даже если ограничить node, теоретически, одним ядром-потоком на все его активности, логически всё равно будет 6 параллельных задач. С т.з. физического параллелизма будет использоваться другая стратегия распределения времени.
I>>Гонки это свойство многозадачности, а не только потоков. Абсолютно любая многозадачность имеет гонки в соответствии со своей природой. МГ>я смотрю ты не понимаешь что такое гонки. Гонки, это когда у тебя выполняется 10 инкрементов, а в результате переменная инкрементируется на 9, или вообще на 1, вот это гонки. А то что ты называешь "гонками" это о бычное асинхронное исполнение. Оно по определению не упорядоченно, но это не гонки и не "параллелизм".
Тут надо вспомнить, что гонки и есть отсутствие упорядочения доступа к разделяемому ресурсу из за неконтролируемых событий, таймингов и тд. Поскольку асинхронное исполнение неупорядочено по времени, то при доступе к разделяемому ресурсу будет плодить гонки. И так вне зависимости от вида многозадачности.
Например — мы знаем, что запросы open выполняются одним за другим, это гарантирует интерпретатор, а вот ответы ядра нода, операционки, могут приходить как им вздумается, в любом порядке, упорядочения нет, правильно? Отсюда и возникают гонки, что и демонстрируется примерами кода.
I>>Гонки при инкременте глобального счетчика — раскрой глаза, я именно этот вариант показал. МГ>Я ещё раз говорю, давай код в котором в ноде будет гонка с инкрементом счётчика. То что ты можешь растекаться по древу мыслями используя собственное понимание общепринятой терминологию я уже понял.
Ты хочешь увидеть частный случай — гонки из за многопоточности. Объект в другой воркер или даже поток можно передавать как по значению, так и по ссылке.
Гонки возможны на всех без исключения видах многозадачности. Собственно, от них в любой многозадачности никуда не деться.
Здравствуйте, Sheridan, Вы писали:
R>>Асинхронные фреймворки есть почти на всем, но в NodeJS асинхронщина изначально закладывалась как обязательная возможность. S>Нет. жс тупо по другому не умеет. Приходится изворачиваться.
Нод принципиально делался асинхронным — именно эта асинхронность и стала причиной появления Нода.
По другому — умеет, например, есть синхронные, т.е. блокирующие вызовы, запросто. Есть воркеры, есть потоки, есть возможность передать туда как по значению, так и по ссылке.
Там где надо, в ноде используются и потоки, и воркеры.
Просто у тебя какое то искаженное видение, что все приложения должны быть с ручным контролем многозадачности и не иначе, как на базе многопоточности.
На самом деле большинство задач в веб-приложениях никакой тяжелой логики не выполняют, просто передают данные с минимальными изменениями. То есть, CPU не является узким местом. Большинство это примерно 8..9 из 10.
То есть, потоки фактически заняты на 80-90% времени ожиданием ответа другой системы, бд, нетворка и тд. Это было уже задолго до Нода.
Именно эта особенность побудила разных людей взяться за эксперименты — разные платформы, разные языки.
Один из этих людей был автор Нода.