LINUX.ORG.RU

Масштабирование приложений на go и scala

 , ,


3

3

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

Скорее всего заказчик будет пускать приложение в кубах, но во-первых - не факт, а во-вторых хотелось бы знать, как выжимать максимум с одного сервера или виртуалки.

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

Lrrr ★★ ()

pm2 и можно указывать кол-во инстансов

есть ли что-то похожее на go

Ололо? Этот убогий костыль для убогой ноды в Go не нужен априори.

А так, да, кубер и юзают.

WitcherGeralt ★★ ()

Ноду так пускают потому, что ядро V8 - однопоточно. А go / scala по настоящему многопоточны из коробки. Так что твой вопрос слегка безсмысленный. Гугли go microservices

menangen ★★★★★ ()

Почему человека, не понимающего, что такое потоки, пустили к разработке масштабируемых приложений?

Для начала почитай букварь вроде Java Concurrency in Practice.

Legioner ★★★★★ ()

Исходя из того, что я знало про go раньше, если в приложении на go в памяти хранится большой объём данных, то может тупить сборщик мусора и в этом отношении он точно хуже JVM и, вероятно, хуже ноды. Это стоит проверить своевременно.

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

Скорее ты не понял их ответы, ибо они говорят всё правильно. В го за счёт абстракции в виде горутин (корутин) можно довольно эффективно использовать любое кол-во ядер в рамках одного сервиса.

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

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

Хорошо. Предположим, написал я на го полсотни корутин. Что набрать, чтобы получить аналог pm2 monit и pm2 plus? Как включить балансировку в стиле pm2 start app.js -i max? "

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

Пойми, в нормальных языках тебе не нужно запускать несколько копий приложения в рамках одного сервера. Go (как и другие конкурентные языки) используют все ядра что доступны без каких-либо конфигураций.

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

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

Я так понимаю, скалу и го к нормальным языкам ты не относишь. У них GC при сборке мусора останавливает все потоки сразу. Если у тебя дохрена мелких объектов создаётся с разным временем жизни и дико фрагментируется память, можно нарваться на феерические лулзы, когда GC сжирает больше процессорного времени чем сам код.

Ну и там балансировка, шардинг, миграции между нодами. Сам к горутинам это будешь приделывать?

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

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

При чём тут копии приложения?

Ещё раз вопросы детально

  • как в го увидеть какие потоки когда запускались, сколько ресурсов потребляют, с какими параметрами выполняются (как в pm2 monit)?
  • как в го получить веб-интерфейс в котором была бы статистика, какой поток сколько раз падал и почему (как в pm2 plus)?
  • как в го сделать, чтобы он запустил столько потоков задачи, сколько есть ядер процессоров и балансировал между ними входящие соединения?
monk ★★★★★ ()
Ответ на: комментарий от monk

как в го увидеть какие потоки когда запускались

В go ты не управляешь потоками ОС, эту работу берёт на себя runtime. Ты управляешь только легковесными потоками(корутины), которые runtime жонглирует по потокам.

сколько ресурсов потребляют

Юзай внешний мониторинг

с какими параметрами выполняются

В корутинах нет такого что ты спрашиваешь (ибо в ней запускается любая функция)

как в го получить веб-интерфейс в котором была бы статистика

юзай прометей, к тому же если ты будешь держать на нескольких серверах копии, то тебе придётся столько же вкладок смотреть и «мониторить»

какой поток сколько раз падал и почему

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

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

Я выше написал что golang может использовать хоть все ядра. По умолчанию использует все. Читай внимательнее.

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

Я так понимаю, скалу и го к нормальным языкам ты не относишь.

Почему, наоборот.

У них GC при сборке мусора останавливает все потоки сразу. Если у тебя дохрена мелких объектов создаётся с разным временем жизни и дико фрагментируется память, можно нарваться на феерические лулзы, когда GC сжирает больше процессорного времени чем сам код.

У го довольно интересный gc. Да, stop world есть, но выполняется довольно быстро, есть ещё фоновая работа вне stop world. Насчет объектов, в го есть классы объектов, по сути округления до какого-то числа. И все объекты одного класса размещаются в одном span. Что имхо хорошо снижает фрагментацию.

Ну и там балансировка, шардинг, миграции между нодами. Сам к горутинам это будешь приделывать?

погоди, там речь идёт про запуск нескольких джс-серверов под одной машиной, т.к. они однопоточны. Балансировка - это внешний элемент, nginx или haproxy. Про шардинг и миграции между нод требуют не все приложения. Ну и в упомянутой scala есть akka, которая сильно упрощает жизнь.

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

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

Чо? Ты сейчас хочешь сказать, что оно ещё и течёт что ли? Или что именно оно делает, если одни объекты ещё используются, а про другие никто не помнит и их надо бы удолить?

погоди, там речь идёт про запуск нескольких джс-серверов под одной машиной, т.к. они однопоточны. Балансировка - это внешний элемент, nginx или haproxy.

Ну, да. А что в этом плохого-то?

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

Юзай внешний мониторинг

Внешний мониторинг может вытащить аргументы корутин го? Примерно как https://pm2.keymetrics.io/images/pm2-monit.png

ибо в ней запускается любая функция

Так аргументы этой функции. Собственно, про то и речь. Если в pm2 что-то работает, то у этого чего-то есть имя, аргументы, время запуска. Если в го, что-то работает, то кроме общей загрузки и некоего абстрактного количества потоков (не совпадающего с числом корутин) ничего получить нельзя.

юзай прометей

Прометей ведь вроде СУБД? Или есть ещё какой-то, который позволяет по корутинам статистику собирать?

Запускай в контейнере, и авторестарт нормальный

Каждую корутину? А так можно?

Я выше написал что golang может использовать хоть все ядра. По умолчанию использует все.

В смысле? Если я запущу одну корутину с net.Listen, то голанг мне её автоматически распараллелит на все ядра? А как он узнает, что это нужно?

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

Чо? Ты сейчас хочешь сказать, что оно ещё и течёт что ли? Или что именно оно делает, если одни объекты ещё используются, а про другие никто не помнит и их надо бы удолить?

Нет, те, что нужно удалить, он освобождает, и когда появляется запрос на выделение нового объекта того же класса, то переиспользует место. Если span пустой, то вполне может освободить память ОС. Но по мониторингу (дефолтные метрики дефолтного экспортера для прометея) он всё равно держит часть освободившейся памяти, вероятно чтобы слишком часто не запрашивать у ОС.

Ну, да. А что в этом плохого-то?

Наверное ничего. Просто я не сторонник такого подхода, я больше за golang/erlang/scala. С легковесными потоками.

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

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

А если объект другого класса? Или объекты имеют разный размер из-за, например, текстового поля?

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

Странно. Мне казалось, сейчас голанг тупо выделяет кусок адресного пространства в 512G и делает в нём что хочет, надеясь, что overcommit включен и OOM не придёт.

Наверное

Наверное? Ты не знаешь, что ли?

Просто я не сторонник такого подхода, я больше за golang/erlang/scala. С легковесными потоками.

И чем же он лучше-то? К слову, ты Erlang вообще сбоку сюда привёл. У него совершенно другой подход к работе с памятью и сборке мусора чем у golang и jvm.

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

Внешний мониторинг может вытащить аргументы корутин го? Примерно как https://pm2.keymetrics.io/images/pm2-monit.png Так аргументы этой функции. Собственно, про то и речь. Если в pm2 что-то работает, то у этого чего-то есть имя, аргументы, время запуска. Если в го, что-то работает, то кроме общей загрузки и некоего абстрактного количества потоков (не совпадающего с числом корутин) ничего получить нельзя.

не встречал. Но корутина это слишком мелкая единица чтобы её отслеживать. Лучше отслеживать приложение целиком (healthcheck)

Прометей ведь вроде СУБД? Или есть ещё какой-то, который позволяет по корутинам статистику собирать?

В прометее используется база данных tsdb. Сам по себе пром это сборщик метрик, которые сохраняет в tsdb. По каждой отдельной корутине нет, в целом со всего runtime да. Кроме того ты сам можешь дополнить необходимые метрики для каждой желаемой для тебя корутины (количество посетителей, количество запросов/ошибок и тд)

Каждую корутину? А так можно?

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

В смысле? Если я запущу одну корутину с net.Listen, то голанг мне её автоматически распараллелит на все ядра? А как он узнает, что это нужно?

Почти так. На примере дефолтного http ролтера. Сама прослушка сокета будет выполняться в одной корутине(иначе это race condititon), но при получении запроса эта исходная корутина будет вызывать обработчик твоего запроса в (!) новой корутине, т.е. принял запрос, моментально скинул его на нового исполнителя и продолжит слушать дальше порт. Если говорим про сырые порты, то тут этот момент делается руками и он довольно прост и элементарен.

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

Чувак, ты вот зря в тред со своими горутинами влез. Тебя сейчас заклюют же :)

не встречал. Но корутина это слишком мелкая единица чтобы её отслеживать. Лучше отслеживать приложение целиком (healthcheck)

Но ты ведь сам начал писать, что горутины – это лучшая штука для распараллеливания. А теперь выясняется, что они не обладают многими необходимыми свойствами. Как так?

Сама прослушка сокета будет выполняться в одной корутине(иначе это race condititon)

Схрена ли там race condition? Ты можешь accept() на один сокет в любом количестве тредов делать, лялекс такое позволяет. Смысла, правда, в этом немного, но гонки тут точно не будет. Только если голанг какую-нибудь свою лажу приплёл.

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

А если объект другого класса? Или объекты имеют разный размер из-за, например, текстового поля?

А если так, то другой класс = другой span. Объекты такие, какими ты их создадишь. Если это один тип, то они будут крутиться в span’ах одного класса.

Странно. Мне казалось, сейчас голанг тупо выделяет кусок адресного пространства в 512G и делает в нём что хочет, надеясь, что overcommit включен и OOM не придёт.

Выделяет для виртуальную память в любом случае. Есть один даже известный костыль для более редкого вызова gc в виде создания 10gib []bytes, который занимает только виртуальную память, а резидентную нет. При этом gc вызывается на удвоении heap’а, поэтому можно заметно сократить вызовы. Вот ссыль: https://blog.twitch.tv/ru-ru/2019/04/10/go-memory-ballast-how-i-learnt-to-stop-worrying-and-love-the-heap-26c2462549a2/

Наверное

Как я написал, я не сторонник таких языков (js, python с его GIL), ушёл в golang и мне норм.

И чем же он лучше-то? К слову, ты Erlang вообще сбоку сюда привёл. У него совершенно другой подход к работе с памятью и сборке мусора чем у golang и jvm.

Я их свёл под одну гребёнку с их подходом в использовании легковесных потоков выполнения, а не общую архитектуру виртуальной машины (erlang & jvm) или самого языка.

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

А если так, то другой класс = другой span. Объекты такие, какими ты их создадишь. Если это один тип, то они будут крутиться в span’ах одного класса.

Ну, то есть течёт?

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

А какая ещё память бывает? Приложения в юзерспейсе про реальную память вообще не имеют и не должны иметь понятия. С точки зрения пользовательской программы, она может полностью быть в свопе.

Я их свёл под одну гребёнку с их подходом в использовании легковесных потоков выполнения, а не общую архитектуру виртуальной машины (erlang & jvm) или самого языка.

Ты не понял. В Erlang другой подход к процессам нежели в scala и golang. Давай начнём с того, что в Erlang у каждого процесса своя куча, поэтому нет никакого stop the world.

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

Чувак, ты вот зря в тред со своими горутинами влез. Тебя сейчас заклюют же :)

Я на ЛОРе сижу почти 10 лет. Здесь не заклюют только за чистый C и Slackware.

Но ты ведь сам начал писать, что горутины – это лучшая штука для распараллеливания. А теперь выясняется, что они не обладают многими необходимыми свойствами. Как так?

Какими и кому необходимыми? Об этом не шло речи.

Схрена ли там race condition? Ты можешь accept() на один сокет в любом количестве тредов делать, лялекс такое позволяет. Смысла, правда, в этом немного, но гонки тут точно не будет. Только если голанг какую-нибудь свою лажу приплёл.

тут ты прав, они придумали. В виде того что есть отдельные потоки(!) не корутины которые отвечают за i/o.

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

не встречал. Но корутина это слишком мелкая единица чтобы её отслеживать. Лучше отслеживать приложение целиком (healthcheck)

Так обсуждение с этого и началось. Как сделать healthcheck на задачи внутри приложения. И было отвечено «в го просто запускаешь таски в горутинах, и планировщик сам их раскидает по нужному числу потоков. Никакие дополнительные библиотеки по дефолту не нужны.».

Вот запустили таски в горутинах. И? «Это слишком мелкая единица чтобы её отслеживать».

По каждой отдельной корутине нет, в целом со всего runtime да.

Уже хорошо. Осталось вернутся к вопросу темы: есть ли в го аналог pm2, который будет запускать эти рантаймы и предоставлять интерфейс для управления ими?

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

Ну, то есть течёт?

почему?

А какая ещё память бывает? Приложения в юзерспейсе про реальную память вообще не имеют и не должны иметь понятия. С точки зрения пользовательской программы, она может полностью быть в свопе.

Да.

Ты не понял. В Erlang другой подход к процессам нежели в scala и golang. Давай начнём с того, что в Erlang у каждого процесса своя куча, поэтому нет никакого stop the world.

Окей, про свою кучу не знал в erlang, не настолько хорошо я в него вникся в своё время. Ну и процессы там не процессы, а акторы.

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

Я на ЛОРе сижу почти 10 лет. Здесь не заклюют только за чистый C и Slackware.

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

Какими и кому необходимыми? Об этом не шло речи.

Я процитирую @monk:

Так обсуждение с этого и началось. Как сделать healthcheck на задачи внутри приложения. И было отвечено «в го просто запускаешь таски в горутинах, и планировщик сам их раскидает по нужному числу потоков. Никакие дополнительные библиотеки по дефолту не нужны.».

В виде того что есть отдельные потоки(!) не корутины которые отвечают за i/o.

Отлично! То есть, если программа делает слишком много i/o, то эти потоки могут повеситься? И что происходит, если там локинг где-то вылез? Это ж тот же GIL, который ты так тут не любишь, только для i/o и вокруг корутин! Непорядок.

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

почему?

Потому что я сделал кучу объектов одного типа. Часть выжила, часть сдохла. Больше не делаю. GC же держит span, как ты выразился, на будущее, которого может и не быть. Я исключительно из твоих слов тут исхожу.

Ну и процессы там не процессы, а акторы.

Процессы там – это именно процессы, никаких акторов на уровне языка нет. Акторы в Erlang – это паттерн такой.

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

Вот запустили таски в горутинах. И? «Это слишком мелкая единица чтобы её отслеживать».

Да, корутина создалась, сделала респонс на реквест, умерла. Прожила считанные микросекунды. Какой смысл её мониторить? А вот в самой корутине на запрос обновить счётчики (ответ сервера, подсчет количества запросов какого-либо пути) это уже другое дело. Тебе не нужно думать про щепки, когда ты можешь получать уже чистые и аггрегированные метрики о работе.

Уже хорошо. Осталось вернутся к вопросу темы: есть ли в го аналог pm2, который будет запускать эти рантаймы и предоставлять интерфейс для управления ими?

Кубернетесь.

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

Да, корутина создалась, сделала респонс на реквест, умерла. Прожила считанные микросекунды. Какой смысл её мониторить?

А если не умерла? Если база, например, подвисла? Ну или баг у тебя в коде, из-за которого при определённом запросе корутина уходит в бесконечный цикл, и этот запрос – один из миллиона?

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

Бест практис говорят что нужно использовать context.Context и не парится.

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

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

Причём тут разработка? Речь идёт о мониторинге этого говна.

Насчёт бесконечных циклов в go, насколько помню, есть инструмент который это ловит.

Он решает задачу останова, хочешь сказать? Вот тебе сценарий: ты слегка обосрался в коде, получил unsigned integer underflow, и у тебя вместо 10 итераций в цикле получилось (uint)-10, то есть очень дохрена. Такое случается на один запрос из миллиона. В результате, этот запрос отваливается по таймауту, а у остальных всё хорошо. Как ты это будешь отлавливать кроме как мониторингом горутин?

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

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

Спасибо, граммар-наци (без иронии). База зависла, контекст закончился, по цепочке всё, от квери до http handler’а отвалится, пользователю вернётся 500-я к примеру, перед смертью handler’а через defer запускается обновление метрик для прометея. Он их считывает, алёртит тебе (если настроишь), смотришь логи.

Причём тут разработку? Речь идёт о мониторинге этого говна.

Выше написал.

Он решает задачу останова, хочешь сказать? Вот тебе сценарий: ты слегка обосрался в коде, получил unsigned integer underflow, и у тебя вместо 10 итераций в цикле получилось (uint)-10, то есть очень дохрена. Такое случается на один запрос из миллиона. В результате, этот запрос отваливается по таймауту, а у остальных всё хорошо. Как ты это будешь отлавливать кроме как мониторингом горутин?

Кстати, посмотрел, это отдельный флаг для запуска, т.ч. не в счёт. Через экспорт метрик + логи.

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

База зависла, контекст закончился, по цепочке всё, от квери до http handler’а отвалится, пользователю вернётся 500-я к примеру, перед смертью handler’а через defer запускается обновление метрик для прометея. Он их считывает, алёртит тебе (если настроишь), смотришь логи.

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

И это мы ещё не дошли до того, что чем больше ядер в системе у тебя, тем больше геморроя от GC. Как минимум поэтому масштабировать горутинами приложение на все ядра – крайне херовая идея.

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

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

средствами стандартной библиотеки.

debug.Stack returns the current stack trace. Stack trace is useful to see how many goroutines are currently running, what they are doing, and whether they are blocked or not

и прочие функции из пакетов debug и runtime

как в го получить веб-интерфейс в котором была бы статистика, какой поток сколько раз падал и почему (как в pm2 plus)?

как уже было сказано, prometheus+grafana - общепринятые инструменты для таких вещей.

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

runtime.GOMAXPROCS(runtime.NumCPU())

в начале программы

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

У них GC при сборке мусора останавливает все потоки сразу.

У Скалы какой-то свой GC, отличный от JVM?

Ну и там балансировка, шардинг, миграции между нодами. Сам к горутинам это будешь приделывать?

Это другая задача.

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

debug.Stack

Это только от текущей горутины стек. Причём в виде byte[].

и прочие функции из пакетов debug и runtime

Вижу, общее количество корутин получить можно. И всё.

runtime.GOMAXPROCS(runtime.NumCPU())

И соединения будут балансироваться? Правда?

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

никаких акторов на уровне языка нет

Ну send и (selective) receive есть таки на уровне языка. Другое дело, что в основном используются поведения OTP, а не такие низкоуровневые штуки.

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

Под процессом я обычно понимаю процесс ОС, поэтому выделяю что акторы, гринтреды, корутины =/= процессам ОС.

Ну я уверен, что @hateyoufeel имел в виду именно процессы (по терминологии) Эрланга, иначе вот это утверждение бы не имело смысла:

в Erlang у каждого процесса своя куча, поэтому нет никакого stop the world

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

ты слегка обосрался в коде, получил unsigned integer underflow

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

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

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

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

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

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

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

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

anonymous ()