LINUX.ORG.RU

Какие накладные расходы для ОС несут блокированные потоки?

 ,


1

4

Ввиду отсутствия нормального акторного фреймворка под Rust, возникла крайне неодназначная по началу идея, которая со временем стала мне казаться довольно любопытной. std::thread + std::sync::mpsc = актор на выделенном потоке. В терминологии akka это актор на пиннед диспетчере. И собственно а почему бы и нет? По каналам сообщения идут в строгом порядке один за одним, поток обладает собственным состоянием, которое не может быть изменено вне хендлера сообщений. Он может реализовывать различные варианты поведения, заменяя их по факту приема сообщений.

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

Ввиду отсутствия нормального акторного фреймворка под Rust

расскажи, чем тебя не устроил Actix.

актор на выделенном потоке.

акторов может быть очень много, потенциально тысячи

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

Тысячи нитей - это ни разу не «огромное количество», по крайней мере. Накладные расходы нити - это ядерный стек (4k, ЕМНИП) + юзерспейсный стек (думаю, не меньше 8k).

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от PPP328

Если речь не о green threads - то проц будет вынужден постоянно переключать контекст

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

потому что 1000 нитей по 4к памяти на каждый в L1-2 кэш уже не влезет.

Даже не знаю, что на это сказать. Во-первых, и не нужно, чтобы они влезали, во-вторых, в L2 влезет.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от tailgunner

расскажи, чем тебя не устроил Actix.

Тем что я уже два года пишу на стеке Scala/Akka. Ну если вкратце - невозможностью изолированного тестирования, невозможностью ясной смены поведения, невозможностью сташинга, отсутствием вотчинга, возможности смены диспетчера и мейлбоксов и много еще чего. И самое главное тем, что библиотека выстроена таким образом, что бы это нельзя было просто и безболезненно дописать.

Serbis ()
Ответ на: комментарий от tailgunner

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

Serbis ()
Ответ на: комментарий от tailgunner

Был у меня такой опыт, только на си. Написал довольно приличный порт Akka Actors вместе с TestKit. Вот сие творение: https://github.com/Serbis/portfolio-mnvp-ca/tree/master/libs/miniakka/src

А это вот рабочий актор из драйвера AODV сети: https://github.com/Serbis/portfolio-mnvp-ca/blob/master/src/a_core.c

Конечный автомат: https://github.com/Serbis/portfolio-mnvp-ca/blob/master/src/a_send_packet_fsm.c

Тесты: https://github.com/Serbis/portfolio-mnvp-ca/blob/master/test/unit/src/preq_transaction_spec.c

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

Serbis ()
Ответ на: комментарий от Serbis

Написал довольно приличный порт Akka Actors

Там в 1.5k SLOC есть «ясная смена поведения, сташинг, вотчинг, возможность смены диспетчера и мейлбоксов и много еще чего»? Если да, то ждем в /r/rust анонс нового акторного фреймворка.

tailgunner ★★★★★ ()
Ответ на: комментарий от WatchCat

С каких пор ulimit депрекатед и что вместо него? По-моему, другого способа ограничить стек просто нет (ну или я его не знаю).

А память под юзерспейсный стек - виртуальная (как вся юзерспейсная память).

tailgunner ★★★★★ ()
Ответ на: комментарий от Serbis

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

Возьмите C++. И со скоростью будет хорошо, и со зрелыми фреймворками ;)

eao197 ★★★★★ ()

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

Во-первых, почему тысячи акторов? Если у тебя так много работы, то может тысяча потоков оправданы?
Во-вторых, к сожалению, в линуксе в 2019 году до сих пор нет аналога SwitchToThread, потому единственным адекватным выходом я вижу временное повышение приоритета вызываемого потока, который после разморозки сразу вызывает возврат приоритета в норму. Все потому, что системный вызов дешевле, чем переключение контекста. А если выполнение с высоким приоритетом не мешает, то можно даже не менять приоритет.
В третьих, юзер-спейс потоки созданы не от хорошей жизни. Async/await в расте уже закопали много лет назад, к слову: https://github.com/aturon/rfcs/blob/remove-runtime/active/0000-remove-runtime.md
«This RFC proposes to remove the runtime system that is currently part of the standard library, which currently allows the standard library to support both native and green threading»

byko3y ()
Ответ на: комментарий от byko3y

что системный вызов дешевле, чем переключение контекста.

Системный вызов для переключения контекста дешевле переключения контекста. Что-то здесь не так.

Async/await в расте уже закопали много лет назад, к слову: https://github.com/aturon/rfcs/blob/remove-runtime/active/0000-remove-runtime.md

Это не async/await, а green threads. Вещь совершенно другая.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Системный вызов для переключения контекста дешевле переключения контекста. Что-то здесь не так

Да, сорян, не в том логическом контексте выразился.
Зеленые треды или просто асинхронный ввод-вывод быстрее тредов ОС, потому что используют чистые системные вызовы, без переключения контекста. SwitchToThread() дает преимущество над простым yield(), поскольку yield неизвестно кому передает контекст.

Это не async/await, а green threads. Вещь совершенно другая.

Без зеленых тредов в async/await смысла просто нет, тогда это получается синтаксический сахар вокруг callback-ов и не более того.

byko3y ()
Ответ на: комментарий от byko3y

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

Для начала, зеленые нити или асинхронный ввод/вывод не быстрее «тредов ОС», потому что и то, и другое работает в рамках «треда ОС».

Без зеленых тредов в async/await смысла просто нет

Почему же?

тогда это получается синтаксический сахар вокруг callback-ов и не более того.

Синтаксический и семантический.

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Без зеленых тредов в async/await смысла просто нет

Почему же?

Потому что абсолютно то же самое можно описать callback-ами.

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

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

byko3y ()
Ответ на: комментарий от byko3y

Без зеленых тредов в async/await смысла просто нет

Почему же?

Потому что абсолютно то же самое можно описать callback-ами.

Зачем описывать коллбеками, если с помощью компилятора можно писать линейный код? Впрочем, это риторический вопрос.

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

Да, Капитан, конечно.

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

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

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

Зачем описывать коллбеками, если с помощью компилятора можно писать линейный код? Впрочем, это риторический вопрос.

Почему вообще async/await должны быть встроенными операциями компилятора, а не явными описаниями на самом языке? Я всю жизнь писал такие конструкции, как два метода/функции, следующие сразу друг за другом. Как бы получается, что у тебя кончается один метод, и сразу начинается связанный код окончания асинхронной обработки, и при этом ясно, что ты закончил обработку здесь и ушел в главный цикл, а вот здесь ты из главного цикла вернулся обратно в обработку. Причина в C# простая - там приходится писать постановку в асинхронную обработку после самого обработчика, как здесь https://i.imgur.com/ubNmxqB.jpg , потому можно распрямить код при помощи async/await https://i.imgur.com/TIgR7vy.jpg . Единственный плюс в данном случае - это упрощенная неявная обработка исключений, что далеко не всегда есть плюс.

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

Если ОП хочет выполнять одну обработку с ветвлением условий, то есть, диспетчер, то да, такая модель подходит, хоть я и не совсем вижу, как async/await упростит разработку. Но я вижу у него множественные акторы на диспетчере, как это сделано в Akka, а там это сделано через однопоточный диспетчер, который только принимает сообщения и отправляет задачи worker-ам из пула потоков, которые уже параллельно выполняют реальную работу акторов.

byko3y ()

Там в 1.5k SLOC есть «ясная смена поведения, сташинг, вотчинг, возможность смены диспетчера и мейлбоксов и много еще чего»?

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

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

Документация к коду просто поражает.

Учитывая количество алкоголя в крови в момент написания кода, удивительно что оно вообще делает то что нужно. Какая уж там документация...

Во-первых, почему тысячи акторов? Если у тебя так много работы, то может тысяча потоков оправданы?

Не в работе дело. Акторы в 3d используются как отображения сущностей на бд и служат кешерующим интерфейсом для предотвращения оверхеда по IO к бд. Простым языком - пришел запрос, сущность восстановилась из бд в виде актора, что-то с ней сделали а актор остался висеть в памяти на некоторое время, что бы когда придет новый запрос, не пришлось выдирать сущность по новой из бд. Если сервис сильно нагружен, то в памяти могут висеть закешированными тысячи сущностей, которые могут ничего не делать, просто ожидая нового запроса.

Serbis ()
Ответ на: комментарий от Serbis

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

З. Ы. Если говорить об akka, то там такая методика работы обходится фантастически дешево, но дает при это огромный плюс по производительности за счет не некоторого увеличения объема потребляемой памяти.

Serbis ()
Ответ на: комментарий от byko3y

Почему вообще async/await должны быть встроенными операциями компилятора, а не явными описаниями на самом языке?

Не очень понимаю, чем отличаются «встроенные операции компилятора» от «явных описаний на самом языке», но async/await в Rust являются (будут являться) конструкциями языка. Специальные конструкции языка нужны для эргономики. Но, наверное, ты сможешь рассахаривать их вручную, если душа просит.

Я всю жизнь писал такие конструкции, как два метода/функции, следующие сразу друг за другом.

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

tailgunner ★★★★★ ()
Ответ на: комментарий от tailgunner

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

Я пробовал читать, но я слабо разбираюсь в расте. Разрыв метода соответствует одному await, насколько я понял.

byko3y ()
Ответ на: комментарий от tailgunner

Эти async/await — это даже не спарки зелёных тредов, это всего-лишь по сути своей генераторы? К чему тогда такие странные названия и всякие понты про await!(TcpStream::connect(...))? Не понимать.

anonymous ()
Ответ на: комментарий от tailgunner

12 метров - только на контексты потоков?

А как интересно планировщик планирует? Вот их 1к потоков. В какой структуре данных они хранятся? Да только полный проход по ней уже выбивает первый уровень кэша не?

pon4ik ★★★★★ ()
Ответ на: комментарий от byko3y

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

Это как вообще? Если зелёные потоки позволяют параллельно считать число Пи в каждом потоке, как это можно делать в обычных потоках, чем зелёные отличаются от обычных?

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Зеленые потоки - это потоки планировщика userspace-а, в противовес планировщику ядра ОС. Переключение зеленого потока не требует ни переключения контекста в ядре, ни системных вызовов. Зеленые потоки используются в некоторых языках, чтобы выделить потенциально параллелизуемые обработки, при этом обычно создание потока делается просто и почти не потребляет ресурсов. Эти потоки могут выполняться как в одном потоке ОС, так и в нескольких, при этом кодеру не важна подобная деталь реализации, он просто создает свои легкие потоки, а планировщик среды раскидывает их по потокам ОС.
В частности, зеленые потоки позволяют легко и непринужденно сделать модель огромного числа потоков, не парясь проблемами чрезмерного потребления ресурсов.

byko3y ()
Ответ на: комментарий от tailgunner

Почему же?

Потому что а зачем?

Когда мы используем async/await, то в момент вызова await у нас начинается ожидание результата некой асинхронной операции, и в этом момент запускается на выполнение следующий зеленый поток на текущем треде.

А если у нас зеленых потоков не будет, то что тред будет делать пока ждет результата? Ничего. А в таком случае какое вообще преимущество будет у async/await перед простыми блокирующими вызовами?

aiive ()
Ответ на: комментарий от byko3y

Async/await в расте уже закопали много лет назад

А вот и неправильно, раст как раз сейчас движется в сторону async/await. И в нестабильной версии все это есть и активно используется, а сейчас просто идет стабилизация всего, на чем async/await реализованы(формат и работа futures, генераторы и тд).

aiive ()
Ответ на: комментарий от byko3y

Без зеленых тредов в async/await смысла просто нет, тогда это получается синтаксический сахар вокруг callback-ов и не более того.

Зеленые треды в виде кооперативной многозадачности в расте есть в виде либы tokio.

aiive ()
Ответ на: комментарий от i-rinat

Это как вообще? Если зелёные потоки позволяют параллельно считать число Пи в каждом потоке, как это можно делать в обычных потоках, чем зелёные отличаются от обычных?

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

aiive ()
Ответ на: комментарий от pon4ik

12 метров - только на контексты потоков?

Да.

Вот их 1к потоков. В какой структуре данных они хранятся?

В списке ядра, наверное. Какая разница?

Да только полный проход по ней уже выбивает первый уровень кэша не?

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

tailgunner ★★★★★ ()
Ответ на: комментарий от i-rinat

Вытесняет их кто? Они сами или ядро?

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

aiive ()
Ответ на: комментарий от aiive

Это совпадает с тем, как я понимаю зелёные потоки. Но @byko3y говорил: «Зеленые потоки позволяют выполнять истинную многопоточную работу, и делают это прозрачно». А вот это совсем не ложится на то, как я это понимаю. Не понятно, что такое «делают это прозрачно». Не понятно, что такое «истинная многопоточная работа». В общем, много вопросов, на которые @byko3y почему-то явно не отвечает.

i-rinat ★★★★★ ()
Ответ на: комментарий от i-rinat

Вытесняет их кто? Они сами или ядро?

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

byko3y ()
Ответ на: комментарий от i-rinat

Зеленые потоки могут выполняться в нескольких потоках процессора - это и есть единственная истинная многопоточность. Все остальное - от лукавого.
Делать прозрачно - значит делать это незаметно. Программист не знает, выполняется ли его программа в одном потоке или нескольких.

byko3y ()
Ответ на: комментарий от i-rinat

Это не async/await, а green threads. Вещь совершенно другая. Погоди-погоди. Чем они отличаются?

Помимо того, что никто не называл и не называет async/await-ом выпиленный 5 лет назад libgreen, еще: 1) кодовой базой (libuv) 2) способом интеграции в рантайм (libuv хочет сама рулить стеками) 3) пользовательским интерфейсом (теми самыми конструкциями async/await, описанными в профильном RFC

Сходи по ссылкам.

tailgunner ★★★★★ ()
Ответ на: комментарий от aiive

Без зеленых тредов в async/await смысла просто нет

Почему же?

Потому что а зачем?

Для асинхронности.

Когда мы используем async/await, то в момент вызова await у нас начинается ожидание результата некой асинхронной операции, и в этом момент запускается на выполнение следующий зеленый поток на текущем треде.

А если у нас зеленых потоков не будет, то что тред будет делать пока ждет результата?

Тред в смысле «thread» переключится на исполнение другого кода.

Ничего.

Ссылку на матчасть я давал - почитай.

tailgunner ★★★★★ ()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Тред в смысле «thread» переключится на исполнение другого кода.

Каким образом? Если у нас все раскидано по потокам оси, то после вызова неблокирующей операции у нас будет только возможность переключится на другой поток оси. Т.е. сделать то-же самое что и так произошло в случае блокирующей операции. А если после вызова неблокирующей операции у нас запускается другой код, то этот другой код по сути и является зеленым потоком.

aiive ()
Ответ на: комментарий от aiive

Если у нас все раскидано по потокам оси

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

. А если после вызова неблокирующей операции у нас запускается другой код, то этот другой код по сути и является зеленым потоком.

По такому определению любой poll порождает зеленый поток. Но это спор о терминах - прикола ради, приведи используемое тобой определение «зеленого потока».

tailgunner ★★★★★ ()