Комментарии 9
ох уж это просачивание абстракций - вместо того, чтобы чинить GC, или улучшать API :
Одно из наиболее интересных предложений заключалось в том, чтобы вообще удалить документацию и тем самым затруднить разработчикам использование sync.Pool.
... странно, что они там не предлагали сделать это же самое прямо на уровне языка (то есть удалить доки про Go <evil-grin.jpg>)
Во-первых, страницы стека горутин аллоцируются на хипе (по этой причине бесконечная рекурсия приводит в Go не к ошибке переполнения стека, а к ООМ).
package test
import (
"testing"
)
func recursive() {
recursive()
}
func TestStackOverflow(t *testing.T) {
recursive()
}
=== RUN TestStackOverflow
runtime: goroutine stack exceeds 1000000000-byte limit
runtime: sp=0xc020170390 stack=[0xc020170000, 0xc040170000]
fatal error: stack overflow
runtime stack:
runtime.throw({0x51f15a?, 0x5e1e80?})
/home/xxx/sdk/go1.18.3/src/runtime/panic.go:992 +0x71
runtime.newstack()
/home/xxx/sdk/go1.18.3/src/runtime/stack.go:1101 +0x5cc
runtime.morestack()
/home/xxx/sdk/go1.18.3/src/runtime/asm_amd64.s:547 +0x8b
Что я делаю не так?
Попробуйте вызвать не как обычную рекурсивную функцию, а как горутину.
Спасибо за интересный вопрос, постараюсь пояснить, что я имел в виду. Под "переполнением стека" я подразумевал классическую проблему stack overflow - ситуацию, при которой процесс полностью исчерпывает всю память в той области виртуального адресного пространства, которая отведена для хранения локальных переменных и адресов возврата. Актуальное значение размера стека можно узнать через параметр VmStk
в /proc/$pid/status
.
Так вот, при бесконечной рекурсии в Go VmStk
вообще никак не изменяется, зато VmRSS
, по которой можно косвенно оценить размер хипа, растёт практически линейно. Если ваш тест поместить в контейнер и ограничить ему потребление RSS до значения, меньшего чем 1000000000-byte limit
(например, 256 Мб), то процесс завершится с обыкновенным OOM.
![](https://habrastorage.org/getpro/habr/upload_files/3cf/67c/c8e/3cf67cc8e5a472d14f8238f487a012ca.png)
Поэтому Go просто эмулирует ошибку переполнения стека, чтобы защититься от ООМ.
Если же перенести ваш пример в С, то окажется, что там стекфреймы хранятся как раз на стеке. Если размер стека превышает дефолтный для Linux лимит в 8 Мб, то случается stack overflow, и программа падает с сегфолтом.
![](https://habrastorage.org/getpro/habr/upload_files/d6c/ddf/dca/d6cddfdcafb7616410a66ce8474eeaf0.png)
Исходники тестов и графиков выложил сюда.
Виталий, спасибо за ваш опыт. Статью добавил в закладки. Попробую.
коллега! почему считаете, что Go отучает от внимательности?
Это субъективное ощущение. Я просто поймал себя на мысли о том, как тяжело мне переключаться на С/С++ после длительного периода работы на Go (а переключаться раз в несколько месяцев всё же приходится). Каждый раз обязательно наступаешь на грабли из стандартного набора (NPD, висячие указатели...). Но думаю, что это не проблема одного Go, это относится ко всем языкам с автоматическим управлением памятью.
Предотвращаем утечки памяти в Go, ч. 1. Ошибки бизнес-логики