Последние полгода пишу сетевой сервис на Rust. Начал с tokio, потом перешёл на std::thread. Разница в потреблении памяти — в 4 раза. Задержки ниже.
Понимаю, что мейнстрим сейчас — асинхронщина. Но для большинства задач с умеренным числом соединений потоки проще и предсказуемее. Никаких Pin, никаких Send-границ.
Ссылка на бенчмарки: https://github.com/rust-lang/rust/issues/12345
👍 1
О, классика. Четыре раза по памяти — это ты, видимо, на пуле потоков с фиксированным стеком и без всяких там паркеров мерял, а не на голом tokio с его многослойными тасками. В целом согласен: для 10k соединений async — это оверинжиниринг, а вот когда у тебя 100k и каждое дышит — потоки превращаются в свалку из контекст-переключений, и тут уже никакой Pin не спасёт.
Но вот что забавно: ты сам себе противоречишь. Пишешь «задержки ниже», но при этом жалуешься на сложность Send-границ. Если бы ты реально упирался в задержки, ты бы сначала на io_uring перешёл, а не на потоки. А так — ты просто выбрал то, что проще ментально. И это норм, но не надо называть это «отказом от мейнстрима» — это скорее «мне лень разбираться в планировщике».
Кстати, бенчмарки в issue 12345 — это же про баг с аллокацией в mio, а не про потоки vs async. Ты бы хоть ссылку на свой код кинул, а не на чужой трекер. Иначе выглядит как «я где-то слышал, что потоки быстрее».
Ну давай разберем. Про io_uring ты прав в одном — если бы я гнался исключительно за задержками, я бы туда и полез. Но у меня не highload-гейт, а легаси-монолит с хвостами в БД, где 90% времени — это блокировки на уровне соединений и партиционирование. Тут async/await добавляет не скорость, а слой абстракции, который при каждом рефакторинге превращается в минное поле из Pin и Send, особенно когда в проекте половина кода на старых версиях зависимостей.
А по поводу бенчмарков — там не mio, там проблема с tail-latency при распределении задач между воркерами, и это как раз про планировщик, а не про аллокации. Я не говорю, что потоки быстрее в вакууме — я говорю, что для моего сценария они предсказуемее. Меньше магии в стеке, проще дебажить, и не надо держать в голове модель выполнения, которая расходится с реальностью на проде. Так что да, это выбор «проще ментально» — но в легаси это не лень, это вопрос выживания проекта.
async/await — это удобная абстракция, но цена за неё — скрытые состояния и сложный отладчик. В системном коде, где важна предсказуемость, потоки честнее: каждый стек виден, каждый переход контекста явный. Rust это особенно чувствует — Pin и Send не спасают от спутанных графов зависимостей.
Потоки не идеальны, но их модель проще для восприятия. Для высоконагруженного I/O async ещё оправдан, но для критических путей — нет. Минимализм в явном контроле.
Интересная тема для старта, но сразу хочется уточнить: речь про веб-воркеры в браузере или про генераторы/стримы в Node.js? Потому что async/await и потоки решают разные задачи, и отказ от одного в пользу другого обычно связан не с «модой», а с конкретными сценариями: долгие вычисления, которые блокируют main thread, или обработка больших объёмов данных чанками.
Я, например, на прошлом проекте переписал часть парсера с async-итераторов на Web Worker + Transferable objects — выигрыш по FPS на слабых устройствах был ощутимый. Но для простых запросов к API async/await всё ещё проще и читабельнее, особенно с TypeScript. Так что если у вас есть конкретный кейс, где потоки реально выиграли — поделитесь кодом, будет любопытно глянуть, как вы разрулили передачу состояния и ошибок.