Pull to refresh

Comments 4

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

То что стандартный дебаггер горутины сам не понимает, это позиция Go-команды: «GDB does not understand Go programs well … it is not a reliable debugger for Go programs» go.dev/doc/gdb. Как я уже писал в статье, горутина это структура в памяти процесса, спящая на канале горутина не лежит ни на одном потоке ОС, поэтому GDB её не видит.

Полумера: GDB умеет info goroutines и goroutine N bt через runtime-gdb.py из поставки Go, но по умолчанию скрипт не грузится, поэтому и не понимает что внутри. Лечится строчкой в ~/.config/gdb/gdbinit:

add-auto-load-safe-path /usr/local/go/src/runtime/runtime-gdb.py

Проверил: стеки спящих горутин показывает, но в списке все горутины выглядят как runtime.gopark, различать приходится по bt.

Есть рабочее решение, это Delve (go install github.com/go-delve/delve/cmd/dlv@latest). Он показывает каждую горутину с пользовательским фреймом и причиной ожидания переключается на заблокированную и печатает Go-типы нормально: jobs = chan main.order 0/0, o = main.order {ID: 1, Price: 9.99}. VS Code и GoLand дебажат через него же.

Подписался на Ваш канал.

Я новичок в Go, но уже яростный сторонник

В посте от 6 июля упоминается

https://siddhantkhare.com/writing/ai-fatigue-is-real

+ Ваша реакция на Торвальдса, подсказывает мне, что Вы поклонник (как и все мы:) AI.

Тогда не обессудьте. Вот критика вашей статьи от Глубокого Больного Пациента

-----------------------

Разбор и резкая критика статьи “Горутины изнутри, часть 1”

Статья претендует на глубокое погружение в устройство горутин и планировщика Go, но, увы, страдает от типичных болезней «околотехнического» контента: перекосы в фактах, излишняя драматизация, подмена понятий и поверхностные бенчмарки. Автор явно старался, но результат получился скорее развлекательным, чем по-настоящему познавательным. Давайте пройдёмся по ключевым косякам.

  1. Сравнение потоков и горутин – манипуляция цифрами

Что заявлено: «Поток стоит 8 МБ виртуальной памяти, переключение ~1.3 мкс, горутина — 2 КБ стека, переключение ~106 нс».

Что на самом деле:

· 8 МБ — это виртуальное адресное пространство, которое резервируется, но не выделяется физически (RSS ~8 КБ). Автор сам это пишет, но продолжает использовать «8 МБ» как пугалку. В реальности это почти бесплатно, пока не начинаешь реально использовать стек. · Переключение потоков в бенчмарке замеряется через pthread_cond_wait/signal — это тяжёлые примитивы синхронизации, включающие мьютексы и системные вызовы. Горутный пинг-понг использует каналы — это легковесный рантайм-объект. Сравнивать их напрямую — некорректно. Если бы автор замерил переключение потоков через futex напрямую (без condvar), цифры были бы другими. · Создание горутины за 390 нс — это создание структуры и стека, но без учёта затрат на планировщик, воровство работы и прочие накладные расходы. В реальном приложении с сотнями тысяч горутин планировщик начинает заметно тормозить, и автор об этом умалчивает.

Итог: цифры красивые, но методологически грязные. Создаётся иллюзия, что горутины в тысячи раз дешевле потоков, хотя разница на практике не столь драматична и сильно зависит от сценария.

  1. Потолок потоков – подмена причин

Что заявлено: «Потоки упираются в лимиты systemd, но можно раздвинуть до 150 тысяч».

Проблема:

· Автор демонстрирует, что на Linux можно создать 150 тыс. потоков, но не упоминает, что каждый поток потребляет как минимум 8 МБ виртуальной памяти, т.е. 150 тыс. — это 1.2 ТБ виртуального адресного пространства. На 64-битной системе это может быть допустимо, но на практике адресное пространство процесса ограничено, и такое количество потоков убьёт производительность из-за промахов TLB и конкуренции за глобальные ресурсы ядра. · В реальных серверах 10-20 тыс. потоков уже создают огромную нагрузку на планировщик ОС, и это не только лимиты, но и кеши процессора, контекстные переключения, блокировки ядра. Автор сводит всё к настройкам, игнорируя физические ограничения.

  1. История GMP – упрощение до карикатуры

Что заявлено: «В Go 1.0 был один мьютекс, всё тормозило, Вьюков придумал P, локальные очереди, воровство работы — и всё стало хорошо».

На самом деле:

· Переход к GMP был эволюционным и включал множество тонкостей: привязка P к M, балансировка глобальной очереди, оптимизация системных вызовов. Автор пересказывает это как «раздали очереди» — слишком примитивно. · Он говорит, что «глобальная очередь осталась запасной площадкой», но не объясняет, как именно работает балансировка, что такое runqsize и почему локальные очереди фиксированы. · Упоминание «воровства работы» без деталей — это просто брендинг. Читатель так и не узнает, как выбирается жертва, как реализован захват половины очереди и какие есть подводные камни (например, конкуренция при краже).

  1. Прерывание горутин – хак или архитектура?

Что заявлено: «В Go 1.2 придумали StackPreempt = 0xfffffade, хак, который работает только на вызове функции, и это оставалось дырой до 1.14».

Критика:

· Называть это «хаком» — не совсем честно. Это стандартный приём во многих рантаймах с кооперативной многозадачностью. Но автор упускает, что в Go 1.14 добавили настоящее вытеснение через сигналы, но оно тоже не панацея (например, циклы с инлайн-кодом без вызовов по-прежнему проблема). · Он пишет: «грабли можно потрогать одной переменной окружения» — но не даёт этой переменной (GODEBUG=asyncpreemptoff=1). Вместо практической информации — пустая интрига. · Пасхалка с -1314 забавна, но не имеет отношения к сути.

  1. Netpoll – не раскрыта цена

Автор хвалится, что netpoll встроен в рантайм, но не упоминает, что он работает только для сетевых сокетов, а для файловых дескрипторов (дисковые файлы) используется блокирующий системный вызов, который всё равно забирает поток ОС. Это важное ограничение, о котором начинающие программисты часто спотыкаются.

  1. Стиль и подача

· Бесконечные «объяснить с» (видимо, ссылки на изображения) — раздражают и затрудняют чтение в текстовом виде. Если уж публикуешь на Хабре, интеграция картинок должна быть грамотной. · Чрезмерное использование кликбейтных формулировок: «Ванга», «предсказания», «запомни свой прогноз» — это отвлекает и снижает доверие. Техническая статья должна быть строже. · Много воды: история про systemd и лимиты, про ulimit — можно было сжать в пару абзацев. · Автор не даёт рекомендаций по применению, не показывает, как отлаживать планировщик, хотя обещает это в третьей части. Пока это лишь длинное вступление.

  1. Ошибки в коде и замерах

· В демо goroutine_cost используется go func() { <-done }() — канал никогда не закрывается, горутины висят вечно. Это валидный пример, но автор не поясняет, что StackInuse включает память стека, а Sys — память у ОС, и что HeapInuse тут не учтена (а она будет расти из-за внутренних структур рантайма). Цифра «601 байт кучи на горутину» взята с потолка, без объяснения. · При замере переключения горутин автор использует GOMAXPROCS=1, но не показывает, как меняются цифры при GOMAXPROCS>1 (а там появляются дополнительные издержки на воровство и синхронизацию).

  1. Отсутствие критического взгляда на саму модель M:N

Автор не задаётся вопросами:

· Как планировщик Go масштабируется на 64+ ядрах? (известно, что глобальная очередь и системные вызовы становятся узким местом). · Почему в Go до сих пор есть sysmon и принудительное вытеснение только для длительных циклов, а не для всех? · Каковы реальные накладные расходы на g и m структуры при миллионе горутин (они ведь тоже в куче)? · Почему не используется sched_yield или другие механизмы?

Вместо этого — восторженное описание того, как круто всё устроено.

Итог

Статья полезна для новичков, которые хотят получить общее представление о планировщике Go, но опасна своей поверхностностью. Автор смешивает виртуальную память с физической, сравнивает несравнимые бенчмарки, упрощает историю до уровня анекдотов и создаёт иллюзию, что горутины — это серебряная пуля.

Резюме: Читать можно, но с огромной долей скепсиса. Если хотите реально разобраться в планировщике — идите в исходники runtime/proc.go, читайте HACKING.md и дизайн-документы, а не пересказы с картинками. А эта статья — типичный пример «популярной механики», где красота изложения жертвует точностью.

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

Спасибо за подписку на tg`шку и за потраченные на меня токены, идея развернуть пост про Торвальдса против меня хороша :-)

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

  • Виртуальные 8 МБ. Под это в статье отведен отдельный замер и вывод в тексте. Восемь мегабайт про адреса, реально поток занимает 8.3 КБ. Вроде я не соврал.

  • condvar против канала. Я мерил то, что реально пишут в коде на C и на Go, а не сисколл против сисколла.

  • 390 нс на горутину. Это не “структура и стек”, а полный круг создания с ожиданием завершения через WaitGroup, планировщик в цифру уже входит.

  • 150 тысяч потоков и 1.2 ТБ. Виртуальных, на 64 битах это около процента адресного пространства, а промахи TLB дают тронутые страницы, а не зарезервированные. Глава ровно этим выводом и заканчивается, дорог поток памятью и временем, а не числом.

  • GMP как карикатура. Выбор жертвы, захват половины очереди и разбор findRunnable по шагам вынесены в третью часть, и в тексте про это стоит прямая оговорка.

  • Хак с 0xfffffade. Слово “хак” мое, за ярлык не держусь. А GODEBUG=asyncpreemptoff=1 не назван специально. Эта переменная главный герой второй части, там как раз Go 1.14 и сигналы.

  • Netpoll. Фраза “почему с файлами на диске такой фокус не проходит” в статье стоит прямым текстом, подробности в третьей части.

  • 601 байт кучи. Это дельта HeapAlloc до и после, поделенная на миллион, куда входят и структуры g; считает ее сама программа из демо, код открыт.

  • GOMAXPROCS=1. Так задумано, и в статье это оговорено отдельно, при большем значении пинг-понг мерит пробуждения между ядрами, а не цену переключения.

  • Нет критики модели M:N. В первой части ее и не будет. Обсуждать масштабирование на 64+ ядрах с теми, кто пять минут назад узнал, что такое поток, рановато. Эти вопросы ждут третьей части.

  • Стиль, Ванга и вода про systemd. Тут не спорю, вкусовщина. Писал для тех, кто вчера написал первый go func(), и предсказания с лестницей лимитов сделаны именно для них.

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

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

Sign up to leave a comment.

Articles