Здравствуйте, Ikemefula, Вы писали:
I>Ты хочешь увидеть частный случай — гонки из за многопоточности. Объект в другой воркер или даже поток можно передавать как по значению, так и по ссылке.
я хочу чтобы ты корректно использовал устоявшиеся термины — параллелизм, асинхронность, многопоточность, многозадачность, это всё разные термины, для разных (хотя иногда и пересекающихся) контекстов. То, о чём ты говоришь называется concurrency. И для таких как ты (толком не разбирающихся в теме, и потому использующие термины к месту и не очень) пишут специальные статьи: https://tproger.ru/explain/concurrency-vs-parallelism/
Здравствуйте, Ватакуси, Вы писали:
В>Щас тебе GIL вспомнят.
Ну такое. Это проблема не языка, а эталонной реализации.
Я бы в числе проблем назвал неочевидный scope имён и неочевидную разницу между statement и expression. Второе я встречал только в паскале в далёкие университетские годы.
Здравствуйте, Мирный герцог, Вы писали:
I>>Ты хочешь увидеть частный случай — гонки из за многопоточности. Объект в другой воркер или даже поток можно передавать как по значению, так и по ссылке.
МГ>я хочу чтобы ты корректно использовал устоявшиеся термины — параллелизм, асинхронность, многопоточность, многозадачность, это всё разные термины, для разных (хотя иногда и пересекающихся) контекстов. То, о чём ты говоришь называется concurrency. И для таких как ты (толком не разбирающихся в теме, и потому использующие термины к месту и не очень) пишут специальные статьи: https://tproger.ru/explain/concurrency-vs-parallelism/
В итоге ты не нашел ошибки в коде, аргументировать разницу в терминах не смог, привел в качестве аргумента картинку из журнала Мурзилка Ты мог бы и не стараться, я и так понял, откуда у тебя термины.
Вот, навскидку:
1. многозадачность есть логическая и физическая. Логическая — как мы организуем. Физическая — как мы исполняем.
здесь уже нюанс — мой код логически можно рассматривать двояко, как одну задачу инкремента разбитую на 6 подзадач, так и шесть независимых задач конкурирующих за общий ресурс. И шесть задач они какие именно — цикл или только его внутренности?
2. многопоточность — стратегия разделения физических ресурсов, т.е. конкретная реализация. Поток = вычислитель и связаные с ним ресурсы, ядро, регистры, стек, контекст. Вместо потока может быть и процесс, и другой комп, и что угодно другое.
Нюанс — нод он однопоточно-многопоточный
3. синхронный, асинхронный код — указывает, как логические задачи перекладываются на физические. Например, один поток выполняет одну задачу. Это синхронный код. А может и иначе — поток может выполнять то одну, то другую, то третью. Это асинхронный код.
Отсюда ясно, что может быть целых 4 варианта
синхронный однопоточный
синхронный многопоточный
асинхронный однопоточный
асинхронный многопоточный
Последовательный-конкурентный-параллельный — стратегия использования времени. Конкурентный, значит в единицу времени прогресс происходит со всеми задачами. Последовательный — только с одной. Параллельный — не просто прогресс, а именно выполнение в единицу времени.
Три нюанса
1 что считать прогрессом, инкремент или полный цикл доступа к ресурсу. Очевидно — полный цикл.
2 параллелизм возможен, о ужас, в однопоточным асинхронном варианте, см. выше! В этом случае параллельными будут логические задачи, если между ними нет зависимостей. Ровно так же и параллелизм в многопоточном одноядерном исполнении.
3 часто, но не всегда, параллелизм есть способ реализации конкурентности
Проверяем — пока одна задача чего то там инкрементит, другие задачи или читают, или пишут. Следовательно, все в порядке.
Теперь гонки — это ситуация с неопределенной последовательностью доступа к разделяемому ресурсу. Причины разные — параллелизм, сеть, что угодно.
В данном случае одного только параллелизма недостаточно, чтобы обеспечить искомый результат. Очевидно — параллелизм приводит к гонкам, которые и наблюдаем.
Нужно ввести явную зависимость между задачами, а именно — одна задача может работать с ресурсом за раз. Тогда за одно и то же время только одна из задач выполняется. А это уже будет не параллельное, а конкурентное исполнение, т.к. исполнение в наличии тольку у одной задачи, хотя прогресс есть у всех.
H>Я бы в числе проблем назвал неочевидный scope имён и неочевидную разницу между statement и expression. Второе я встречал только в паскале в далёкие университетские годы.
I>То есть, мы здесь запустили 6 параллельных цепочек, каждая из которых работает с одним и тем же разделяемым ресурсом.
Секундочку, допустим мы работаем с диском. Тогда код выше последовательно поставит в очередь контроллера диска какие-то операции. А вот что будет дальше, я
По идее также последовательно они и будут выполняться. Где здесь параллельность?
Далее, допустим мы инкрементируем какую-то глобальную переменную. В данном случае никакой параллельности нет, и rc тоже нет, ибо поток исполнения у нас 1.
Вот если у нас будет сеть и непредсказуемая задержка (latency), тогда да, предсказать кто раньше кто позже будет сложно.
Здравствуйте, Sharov, Вы писали:
S>Секундочку, допустим мы работаем с диском. Тогда код выше последовательно поставит в очередь контроллера диска какие-то операции. А вот что будет дальше, я S>По идее также последовательно они и будут выполняться. Где здесь параллельность?
Ты запускать пробовал или тебе предпочтительно теоретизировать?
Код выше поставит какие то операции в очередь, но далеко не факт, что ответ от io придет в той же последовательности. По какой причине — дело десятое, надо копаться во внутренностях ядра нода, ос, драйверов и тд и тд.
S>Далее, допустим мы инкрементируем какую-то глобальную переменную. В данном случае никакой параллельности нет, и rc тоже нет, ибо поток исполнения у нас 1.
Неверно. В это время io занят выполнением файловых операций.
S>Вот если у нас будет сеть и непредсказуемая задержка (latency), тогда да, предсказать кто раньше кто позже будет сложно.
Задержка между запросом на чтение/запись и ответом непредсказуема. Попробуй поиграть с кодом, хотя бы запустить его, и увидишь своими глазами.
Здравствуйте, Ikemefula, Вы писали:
S>>Секундочку, допустим мы работаем с диском. Тогда код выше последовательно поставит в очередь контроллера диска какие-то операции. А вот что будет дальше, я S>>По идее также последовательно они и будут выполняться. Где здесь параллельность? I>Ты запускать пробовал или тебе предпочтительно теоретизировать?
Теоретезировал. Пытаюсь понять теорию.
I>Код выше поставит какие то операции в очередь, но далеко не факт, что ответ от io придет в той же последовательности. По какой причине — дело десятое, надо копаться во внутренностях ядра нода, ос, драйверов и тд и тд.
Мне бы честно, хотелось бы узнать причины, по которой ответ придет в разом порядке. Учитывая, что в конкретном примере мы работаем с одним и тем же файлом.
Если файлы разные, тогда да, параллельность имеется.
S>>Далее, допустим мы инкрементируем какую-то глобальную переменную. В данном случае никакой параллельности нет, и rc тоже нет, ибо поток исполнения у нас 1. I>Неверно. В это время io занят выполнением файловых операций.
Причем здесь io, если в ноде принципиально отсутствует многопоточность?
S>>Вот если у нас будет сеть и непредсказуемая задержка (latency), тогда да, предсказать кто раньше кто позже будет сложно. I>Задержка между запросом на чтение/запись и ответом непредсказуема. Попробуй поиграть с кодом, хотя бы запустить его, и увидишь своими глазами.
Про параллельность в случае сети и разных файлов я вовсе не спорил. Спор про один и тот же файл и счетчик в памяти.
Здравствуйте, Sharov, Вы писали:
S>Теоретезировал. Пытаюсь понять теорию.
А что мешает взять да запустить?
I>>Код выше поставит какие то операции в очередь, но далеко не факт, что ответ от io придет в той же последовательности. По какой причине — дело десятое, надо копаться во внутренностях ядра нода, ос, драйверов и тд и тд.
S>Мне бы честно, хотелось бы узнать причины, по которой ответ придет в разом порядке. Учитывая, что в конкретном примере мы работаем с одним и тем же файлом.
Я даже не представляю, каким образом это можно выявить. Недетерминизм можно ождать откуда угодно — внутренности нода, ос, драйверов, кеш, протокол работы с железом, шина, сам контроллер устройства
S>>>Далее, допустим мы инкрементируем какую-то глобальную переменную. В данном случае никакой параллельности нет, и rc тоже нет, ибо поток исполнения у нас 1. I>>Неверно. В это время io занят выполнением файловых операций.
S>Причем здесь io, если в ноде принципиально отсутствует многопоточность?
Нод — многопоточный! Это его основная фишка. Однопоточный только исполнитель джаваскрипта. Именно исполнитель, т.к. сам интерпретатор состоит из многих вещей, которые в т.ч. многопоточны, например, гц. Пока жээс инкрементит, унутре идут файловые операции.
Здравствуйте, Ikemefula, Вы писали:
I>Здравствуйте, Sharov, Вы писали:
S>>Теоретезировал. Пытаюсь понять теорию.
I> А что мешает взять да запустить?
1)Хочется разобраться в теории.
2)Пока не вижу необходимости -- в принципе, со всем, что Вы тут пишите я в целом согласен. Есть теор. нюансы, которые надо бы прояснить.
3)Лень.
S>>Мне бы честно, хотелось бы узнать причины, по которой ответ придет в разом порядке. Учитывая, что в конкретном примере мы работаем с одним и тем же файлом. I>Я даже не представляю, каким образом это можно выявить. Недетерминизм можно ождать откуда угодно — внутренности нода, ос, драйверов, кеш, протокол работы с железом, шина, сам контроллер устройства
А вот надо представлять! Иначе какой в этом смысл? Разве будет недетреминизм, если мы в очередь поставили обращение к одному и тому же файлу -- чтение, запись? Мне кажется, все будет последовательно. Соотв. результат будет аналогичен await.
S>>Причем здесь io, если в ноде принципиально отсутствует многопоточность? I>Нод — многопоточный! Это его основная фишка. Однопоточный только исполнитель джаваскрипта. Именно исполнитель, т.к. сам интерпретатор состоит из многих вещей, которые в т.ч. многопоточны, например, гц. Пока жээс инкрементит, унутре идут файловые операции.
Вот именно однопоточный исполнитель и будет инкрементировать переменную в памяти. Т.е. никаких rc в принципе. Acинхронный io да, сколько угодно.
Здравствуйте, Sharov, Вы писали:
I>>Я даже не представляю, каким образом это можно выявить. Недетерминизм можно ождать откуда угодно — внутренности нода, ос, драйверов, кеш, протокол работы с железом, шина, сам контроллер устройства
S>А вот надо представлять! Иначе какой в этом смысл? Разве будет недетреминизм, если мы в очередь поставили обращение к одному и тому же файлу -- чтение, запись? Мне кажется, все будет последовательно. Соотв. результат будет аналогичен await.
Ты продолжаешь теоретизировать. Запусти и сам увидишь, что никакого последовательного доступа нет. Каждый раз разные последовательности.
Важно иметь гарантии серилизованого доступа. И тут важно знать природу этого механизма — кто серилизует. А вот природа недетерминизма как раз так не сильно интересует.
S>>>Причем здесь io, если в ноде принципиально отсутствует многопоточность? I>>Нод — многопоточный! Это его основная фишка. Однопоточный только исполнитель джаваскрипта. Именно исполнитель, т.к. сам интерпретатор состоит из многих вещей, которые в т.ч. многопоточны, например, гц. Пока жээс инкрементит, унутре идут файловые операции.
S>Вот именно однопоточный исполнитель и будет инкрементировать переменную в памяти. Т.е. никаких rc в принципе. Acинхронный io да, сколько угодно.
Гонки есть в каждой виде многозадачности. Такой вот неприятный факт. И совсем неважно, какая она — синхронная многопоточная или асинхронная однопоточная, вытесняющая или кооперативная.
Разница только в том, как именно выглядят эти гонки там и там.
Гонки возникают в том случае, когда хотя бы одна из операций в протоколе доступа к разделяемому ресурсу неатомарна. В нашем случае read и write не обеспечивают атомарность. Соотвенно из за параллелизма получаем, например, write одной задачи вмешивается в read другой задачи.
У тебя почему то задача == поток. Это крайне неверное утверждение.
Здравствуйте, Ikemefula, Вы писали:
I>Ты продолжаешь теоретизировать. Запусти и сам увидишь, что никакого последовательного доступа нет. Каждый раз разные последовательности. I>Важно иметь гарантии серилизованого доступа. И тут важно знать природу этого механизма — кто серилизует. А вот природа недетерминизма как раз так не сильно интересует.
Самое интересное и важное. Именно вот это св-во асинхронность и эксплуатирует. Нужно же знать степень недетерминизма. Если его нет совсем, то как async io поможет?
I> Гонки есть в каждой виде многозадачности. Такой вот неприятный факт. И совсем неважно, какая она — синхронная многопоточная или асинхронная однопоточная, вытесняющая или кооперативная. I>Разница только в том, как именно выглядят эти гонки там и там. I>У тебя почему то задача == поток. Это крайне неверное утверждение.
Вы мне чего-то приписываете странное. Я утверждаю, что при "однопоточном исполнителе" никаких гонок при инкрементирование переменной в памяти (это не io!!!) нету.
Параллельность io задач это никак не отменяет.
> В нашем случае read и write не обеспечивают атомарность. Соотвенно из за параллелизма получаем, например, write одной задачи вмешивается в read другой задачи.
При "однопоточном исполнителе" это не играет роли.
Здравствуйте, Sharov, Вы писали:
I>>Важно иметь гарантии серилизованого доступа. И тут важно знать природу этого механизма — кто серилизует. А вот природа недетерминизма как раз так не сильно интересует.
S>Самое интересное и важное. Именно вот это св-во асинхронность и эксплуатирует. Нужно же знать степень недетерминизма. Если его нет совсем, то как async io поможет?
Это гарантии серилизованого доступа только наоборот.
I>>Разница только в том, как именно выглядят эти гонки там и там. I>>У тебя почему то задача == поток. Это крайне неверное утверждение.
S>Вы мне чего-то приписываете странное. Я утверждаю, что при "однопоточном исполнителе" никаких гонок при инкрементирование переменной в памяти (это не io!!!) нету.
Я про ресурс, а ты про переменную Определяет не наличие переменной, а протокол доступа. При простом инкременте операция чтение-изменение-запись может быть атомарной. А вот если мы разнесем во времени эти три составляющие, то даже с одной переменной будут проблемы.
>> В нашем случае read и write не обеспечивают атомарность. Соотвенно из за параллелизма получаем, например, write одной задачи вмешивается в read другой задачи.
S>При "однопоточном исполнителе" это не играет роли.
Однопоточных исполнителей минимум два варианта. Поточность это всего лишь способ упаковки логических задач на физический исполнитель.
1. синхронный однопоточный
2. асинхронный однопоточный
В первом варианте с ресурсом все будет хорошо — один поток, ничего, кроме одной задачи он выполнять не может. Одна задача работает от начала до конца, другие гарантировано стоят неначавшись.
Во втором варианте все будет плохо — разные задачи выполняются передавая друг другу управление через эвент луп, фактически — кооперативная многозадачность.
Здравствуйте, Ikemefula, Вы писали:
S>>Самое интересное и важное. Именно вот это св-во асинхронность и эксплуатирует. Нужно же знать степень недетерминизма. Если его нет совсем, то как async io поможет? I>Это гарантии серилизованого доступа только наоборот.
Можете мысль развернуть?
S>>Вы мне чего-то приписываете странное. Я утверждаю, что при "однопоточном исполнителе" никаких гонок при инкрементирование переменной в памяти (это не io!!!) нету. I>Я про ресурс, а ты про переменную Определяет не наличие переменной, а протокол доступа. При простом инкременте операция чтение-изменение-запись может быть атомарной. А вот если мы разнесем во времени эти три составляющие, то даже с одной переменной будут проблемы.
Я уже давно про переменную и про доступ к ней при "однопоточном исполнителе". Про ресурсы(io) мы пришли к консенсусу -- параллелилизм наличествует.
Я не до конца понимаю, а разве инструкция i+=1 может быть прервана? Ну допустим, мы убрали контекст задачи инкрементации, где в регистре процессора
у нас лежит i+1, но в память еще не записали, переключились на контекст callback'а . Ну прочитает callback значение i, ну и, в чем проблема? i+1 мы еще не опубликовали, это в чьем то стеке\регистре процессора и т.д. А как только i+1 опубликуем, так все последующие callback'и будут читать i+1. Т.е. никакой неконсистентности тут нет. Вполне наличествует сериализуемость.
Здравствуйте, Sharov, Вы писали:
S>>>Самое интересное и важное. Именно вот это св-во асинхронность и эксплуатирует. Нужно же знать степень недетерминизма. Если его нет совсем, то как async io поможет? I>>Это гарантии серилизованого доступа только наоборот.
S>Можете мысль развернуть?
Цитата "степень недетерминизма" @ — это серилизованый доступ наоборот. Проще говорить о серилизованом доступе, есть он, нет его, будет ровно то же.
Нас интересует не сам недетерминизм, а паралеллизм. Сам недерерминизм это просто сопутствующий эффект.
I>>Я про ресурс, а ты про переменную Определяет не наличие переменной, а протокол доступа. При простом инкременте операция чтение-изменение-запись может быть атомарной. А вот если мы разнесем во времени эти три составляющие, то даже с одной переменной будут проблемы.
S>Я уже давно про переменную и про доступ к ней при "однопоточном исполнителе". Про ресурсы(io) мы пришли к консенсусу -- параллелилизм наличествует. S>Я не до конца понимаю, а разве инструкция i+=1 может быть прервана? Ну допустим, мы убрали контекст задачи инкрементации, где в регистре процессора
Мы можем протокол заменить на другой — прочитали, передали дальше, поймали, вычислили, передали дальше, поймали, записали.
S>у нас лежит i+1, но в память еще не записали, переключились на контекст callback'а . Ну прочитает callback значение i, ну и, в чем проблема? i+1 мы еще не опубликовали, это в чьем то стеке\регистре процессора и т.д. А как только i+1 опубликуем, так все последующие callback'и будут читать i+1. Т.е. никакой неконсистентности тут нет. Вполне наличествует сериализуемость.
Это потому, что протокол такой. А если протокол поменять, что я продемонстрировал кодом, то сразу ломается всё подряд.
Здравствуйте, Ikemefula, Вы писали:
S>>Я уже давно про переменную и про доступ к ней при "однопоточном исполнителе". Про ресурсы(io) мы пришли к консенсусу -- параллелилизм наличествует. S>>Я не до конца понимаю, а разве инструкция i+=1 может быть прервана? Ну допустим, мы убрали контекст задачи инкрементации, где в регистре процессора I>Мы можем протокол заменить на другой — прочитали, передали дальше, поймали, вычислили, передали дальше, поймали, записали.
Какой протокол? Вы что в это слово вкладываете?
S>>у нас лежит i+1, но в память еще не записали, переключились на контекст callback'а . Ну прочитает callback значение i, ну и, в чем проблема? i+1 мы еще не опубликовали, это в чьем то стеке\регистре процессора и т.д. А как только i+1 опубликуем, так все последующие callback'и будут читать i+1. Т.е. никакой неконсистентности тут нет. Вполне наличествует сериализуемость. I>Это потому, что протокол такой. А если протокол поменять, что я продемонстрировал кодом, то сразу ломается всё подряд.
Можно код, который сломает i+=1 при "однопоточном исполнителе"?
Здравствуйте, Sharov, Вы писали:
I>>Мы можем протокол заменить на другой — прочитали, передали дальше, поймали, вычислили, передали дальше, поймали, записали.
S>Какой протокол? Вы что в это слово вкладываете?
Да всё то же, что и всегда.
Ты что, не видишь разницы между инкрементом переменной в памяти и инкрементом переменной в файле? Это ресурсы с разным протоколом доступа. И я показал, что именно там происходит. Что еще тебе надо ?
Вот если вместо асинхронного чтения-записи использовать синхронное, т.е. изменение протокола, все гонки устраняются.
S>>>у нас лежит i+1, но в память еще не записали, переключились на контекст callback'а . Ну прочитает callback значение i, ну и, в чем проблема? i+1 мы еще не опубликовали, это в чьем то стеке\регистре процессора и т.д. А как только i+1 опубликуем, так все последующие callback'и будут читать i+1. Т.е. никакой неконсистентности тут нет. Вполне наличествует сериализуемость. I>>Это потому, что протокол такой. А если протокол поменять, что я продемонстрировал кодом, то сразу ломается всё подряд. S>
S>Можно код, который сломает i+=1 при "однопоточном исполнителе"?
Если буквально такой вот код, то никакой, потому что один поток выполняет ровно одну операцию и никто больше на это не влияет. Глаза то раскрой — мои два примера именно про это, только ресурс не переменная в памяти, а внутри файла. Все что делает каждая из задач — инкрементит переменную. Только протокол доступа другой. Какой именно — надо взять, посмотреть, запустить и сравнить результаты двух-трех запусков.
Ты можешь заменить операции с файлом, скажем на такие
async read(): number { return i }
async write(value: number) { i = value }
Собственно, атомарными будут только чтение переменной и её запись, весь цикл — чтение-инкремент-запись будет неатомарным, а следовательно, несколько параллельных цепочек сломают инвариант.
А вот если убрать async то все стает в норму. Проблема только в том, что теперь циклы будут выполняться последовательно.
S>>Можно код, который сломает i+=1 при "однопоточном исполнителе"?
I>Если буквально такой вот код, то никакой, потому что один поток выполняет ровно одну операцию и никто больше на это не влияет. Глаза то раскрой — мои два примера именно про это, только ресурс не переменная в памяти, а внутри файла. Все что делает каждая из задач — инкрементит переменную. Только протокол доступа другой. Какой именно — надо взять, посмотреть, запустить и сравнить результаты двух-трех запусков.
I>Ты можешь заменить операции с файлом, скажем на такие
I>
I>async read(): number { return i }
I>async write(value: number) { i = value }
I>
Это уже не "однопоточном исполнителе"
Но если
var i=await read();
await write(++i);
будут вызываться только в одном методе (await гарантирует последовательность вызовов из разных потоков)
Читай и записывай хоть откуда. Тоже, что и при синхронном.
и солнце б утром не вставало, когда бы не было меня
Здравствуйте, Serginio1, Вы писали:
I>>Ты можешь заменить операции с файлом, скажем на такие
I>>
I>>async read(): number { return i }
I>>async write(value: number) { i = value }
I>>
S>Это уже не "однопоточном исполнителе"
Это обычный джаваскрипт, его исполнение однопоточно. Но это асинхронный код, хотя здесь и нет никакого IO. Соответственно, гонки — в полный рост и именно там, откуда и стоит ждать.