Size: a a a

2021 April 22

AP

Anton Petrusevich in Modern::Perl
если есть преемпшен, то асинхронщина под большим вопросом необходимости
источник

AP

Anton Petrusevich in Modern::Perl
да, я когда-то (лет 17-18 назад) писал мультитредовые приложения, где в каждом треде асинхронщина. ну, работало, эффективно вполне.
источник

AP

Anton Petrusevich in Modern::Perl
но то были времена, когда треды ресурсы кушали и их больше нескольких тыщ было сложно создать
источник

AP

Anton Petrusevich in Modern::Perl
весь процессор — стейт-машина.
источник

AP

Anton Petrusevich in Modern::Perl
источник

SZ

Sergey Zhmylove in Modern::Perl
It depends. Некоторые задачи на тредах проиграют из-за межпоточного взаимодействия
источник

DF

Denis F in Modern::Perl
За такой ценник и электричество можно нехилую стойку тесл собрать. Она его ещё и порвёт по скорости.
источник

VO

Vyacheslav Olkhovche... in Modern::Perl
Треды и сейчас ресурсы кушают. Тот же стек например
источник

W

Warstone in Modern::Perl
Теслы не панацея. Они сильно специализированные. А если их использовать как процы внезапно Тесла становится как 2-4 небыстрых 8ядерных атома или i3 5-10 летней давности.
источник

AS

Alexey Stavrov in Modern::Perl
Оооо, дак вот оно как работает await в с#, а вы безусловно по ходу нашего разговора именно это пытались объяснить с самого начала, показывая, какой вы знаток работы с#, что знаете, как N тасков "мапятся" на M тредов (M < N) в этом языке.

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

Вообще я тут подумал, мне стало и так +/- понятно на мой вопрос ответ (на вопрос: "как же стейт машина в с# обрабатывает запросы, если там нет poll/select/epoll/kqueue и каких-то других схожих примитивов") и без замечательного специалиста по работе c#. Напишу для интересующихся, которые проглотили заманчивый высер, а в итоге получили фигу.

Краткий ответ - никак. Такого рода примитив должен быть, а так же должен быть этот цикл while явно или неявно, т.е. взять ваш код на c#, а потом при компиляции сделать вокруг него класс с полями, где каждая переменная будет свойством класса и вызовы await в этом коде делят его на состояния, в которое он попадает замечательным switch (state) case "x": ... ничего не решает. Более подробно ниже, кому инетерсны умозаключения.

Представим код, который пытается отдать управление с помощью слов await/yield в многопоточном коде. Yield в данном случае будет являться не генератором, а методом или спец. функцией для планировщика задач, которая просит планировщика сменить задачу на данном потоке, так как текущая задача по каким-то причинам хочет уступить выполнение (ждёт ответа из сети или просто "хочет отдохнуть":-)). В этом случае поток выполнения будет переключаться на другой таск. А теперь представим, что тасков больше нет, т.е. вот у вас 2 таска, которые чего-то ждут по сети/с диска. Если вы будете переключаться между этими тасками, то будете получать так же 100% CPU load и не важно, стейт машина там или нет, ведь выполнять всё равно код каждого стейта дальше нельзя, так как данные не готовы. Что делать в этом случае? Скорей всего нужно иметь очередь задач, ожидающих выполнение + массив задач, которые suspended (ожидают чего-то), а так же спец. задачу, цель которой как раз и сделать этот вызов poll/select/... Назовём эту задачу так: X (для простоты написания монолога). Соответственно задача, которая suspended, отсутствует в очереди на выполнение, а задача X должна каким-то образом планироваться всё время. Вопрос в том, кто планирует задачу X? Самое простое решение такое: пусть она сама себя планирует, т.е. в конце эта задача сама себя помещает в очередь на выполнение. В итоге мы имеем этот цикл while, только неявно. Итого, что у нас имеется:
- таски, которые выполняется тогда, когда готовы
- пул потоков
- таски брать на выполнение может любой поток, который закончил выполнять у себя текущий таск
- особый таск Х, который занимается проверкой готовности ответа из сокета и планирует задачу, которая связана с этим сокетом, а так же планирует себя
- распределение N задач по M тредам

Таск Х может иметь такую логику, в дополнении к логике, которая есть в цикле событий простого планировщика, как в mojo:
можно не делать вызов poll с sleep_timeout > 0, если есть в очереди на выполнения задачи. В данном случае сделать = 0, позволяя просто узнать, какие таски можно вернуть в очередь на выполнение, и пойти "разгребать" очередь задач на выполнение.

Почему же я решил, что вместо epoll/select/... и цикла while нет ничего другого? Ну скажем так, я за долгое существование c#/go/rust и других не слышал других механизмов и системных вызовов, а значит с большой вероятность механизм тот же. Всё равно должен быть блокирующий вызов, который не тратит ресурсы системы в пустую, блокируясь, когда в очереди на выполнение нет задач.
источник

AS

Alexey Stavrov in Modern::Perl
Отдельно стоит отметить, что задача Х одна, а потоков может быть много. Если задачу X взял 1-ый поток, то что делать другим потокам, которым нечего взять из очереди на выполнение, иначе они тоже будут расходовать циклы CPU? Предлагаю сделать один из вариантов:
1. yield, но уже у текущего потока (это такой способ сказать ядру ОС через системный вызов, чтобы убрать данный тред с ядра и дать ядру другую задачу)
2. запустить ещё раз задачу Х, правда в данном случае нужно уточнить можно ли делать epoll/poll... одновременно с разных потоков
3. сидеть на condvar остальным тредам и ждать notify/notify_all

Тут можно заметить, что есть "нечестность" (unfair) в обработке тасков и распределения ресурсов. Моя задача была показать, что стейт машина сама по себе ничего не решает, а не изобретать "честный" планировщик задач. Оставим это разработчикам языков и ОС.

Зачем нужна стейт машина?
Я давно слышал от авторитетных людей, что реализация в c# является эталонной и она построена на этой стейт машине. Мол это эталонный и самый "правильный" вариант реализации конкурентности. Именно поэтому в с++20 (и в других языках тоже) сделали ровно так и при этом ~10 лет думали, как же это сделать правильно. Там, где не так, это однопоточные скриптовые языки типа python, там через синтаксический сахар это сделано вокруг yield и yield from (но это не просто синтаксический сахар, но в первом приближении так) или языки типа go, где fullstack файберы, что считается не очень хорошо, видимо из-за манипуляции с стеками. А данный подход с стейт машиной, по всей видимости, позволяет выделить память в куче и держать стейт в куче, а не манипулировать множеством стеков, что, видимо, менее ресурсозатратно + архитектурно хорошо выглядит.

Кто-то может быть обескуражен слову yield, который ничего общего не имеет с генератором в питоне и употреблялся уже дважды выше в данном комментарии с разным контекстом. Ну что я могу сделать, yield такой многогранный. Да, await можно эмулировать, через yield, который генератор, поищите в google видео python concurency from the ground.
источник

AS

Alexey Stavrov in Modern::Perl
Надо придумать политкорректный ответ для чуваков, вытаскивающих свою пипиську на всю улицу со словами "Эгегей! мой член длинее". Спорить без аргументов - так себе.
источник

SZ

Sergey Zhmylove in Modern::Perl
Вообще-то, этот ваш «особый тред» вполне может просто сделать sleep 0.1
источник

AS

Alexey Stavrov in Modern::Perl
Ага, а чо ж тогда никто sleep 0.1 не делает?)
Ответ: потому что пока вы спите 100мс у вас накопилось 100 000 задач.
источник

SZ

Sergey Zhmylove in Modern::Perl
Или просто висеть на любом примитиве синхронизации
источник

SZ

Sergey Zhmylove in Modern::Perl
И это будет вполне норм работать
источник

AS

Alexey Stavrov in Modern::Perl
Да, на condvar могут висеть остальные треды. Но хотя бы один должен выполнять таск X.
источник

SZ

Sergey Zhmylove in Modern::Perl
Нет
источник

SZ

Sergey Zhmylove in Modern::Perl
Не обязательно
источник

AS

Alexey Stavrov in Modern::Perl
Аргументы?)
источник