Стендап Сьогодні 📢 Канал в Telegram @stendap_sogodni

🤖🚫 AI-free content. This post is 100% written by a human, as is everything on my blog. Enjoy!

24.05.2023

Мета одночасного програмування — застосувати всі ядра процесора

…Коли я писав свій перший сервіс на Go, мене захопила легкість, з якою там створюються паралельні процеси — горутіни. Мені як раз треба було обчислювати структуру, що складалась з більш ніж 1000 значень. Чудове застосування для горутін, думав я, та створював в циклі всі 1000 штук — по одній на значення.

Через декілька днів після запуску сервіс почав падати через нестачу памʼяті. Виявилось, що хоч горутіни є легковажними, та їх можна створити багато, але тисяча горутін, помножена на сотні паралельних запитів, споживала всю наявну памʼять. Та коли колега поділився статтею про створення мільйона горутін, вона підняла ці спогади.

Моє бачення в тому, що абстракції одночасного програмування існують не для того, щоб “розділити незалежні обчислення” чи “щось швидше обчислити”. Одночасне програмування розв’язує задачу ефективного використання процесора. Та, хоч технічно горутін можна створити навіть мільйон, це буде така сама помилка програміста, як і алгоритми експоненційної складності й інше. Суто теоретично воно має сенс, але на реальному залізі так робити ніколи не треба.

Реальний процесор може виконувати паралельно тільки обмежену кількість операцій. Якщо мовити про сервери, то типова реальна машина — не більше 4, а в абсолютному максимумі - 64. Ніяк не тисячі. Створення тисяч одночасних процесів не прискорить їх виконання, а тільки впровадить невизначеність, оскільки ти більше не контролюєш порядок виконання. (Не кажучи про проблеми з памʼяттю.)

Якщо треба виконати багато обчислень в одночасному режимі, створюється [пул]`(https://github.com/panjf2000/ants) за кількістю ядер процесора. Пул саме й дозволяє використати всі ядра, та нічого більше. Саме до такого рішення я й перейшов після того, як мій сервіс звалювався під вагою тисяч горутін.

Якщо наш додаток делегує роботу базі даних, то в пулі може бути більше процесів, ніж ядер, бо процеси будуть чекати (це якщо база на іншій машині.) Більше настільки, наскільки багато часу процес проводить в базі; якщо третину часу, то на третину більше. Задача та сама — пустити в дію всі ядра більшість часу.

Окремо стоїть славнозвісна мірка мови — підтримка тисяч підключень. Що точно не потребує окремого процесу, це мережеве підключення. Бо багато підключень — це абстракція. Насправді у компʼютера, навіть у сервера, тільки одне, послідовне підключення до зовнішнього світу. Скільки б не було клієнтів, процес на них потрібний тільки один. Буферів в памʼяті треба багато, це так. Та типовий сервер обробляє їх послідовно, завдяки виклику select(). Для паралелізації буде 2, 4, 8 послідовних обробників select(). Мати по процесу на підключення — ще одна хибна абстракція.