thank_go | Unsorted

Telegram-канал thank_go - Thank Go!

2615

Здравый взгляд на язык программирования Go. Злой админ @nalgeon. Добрый админ @mikeberezin. Рекламы нет.

Subscribe to a channel

Thank Go!

Go 1.26: Криптография без ридеров

Пожалуй, самое спорное изменение в Go 1.26.

Сейчас криптографические API (например ecdsa.GenerateKey), обычно принимают io.Reader как источник случайных данных:

ecdsa.GenerateKey(elliptic.P256(), rand.Reader)


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

Команда Go выбрала радикальное решение этой проблемы.

Начиная с версии 1.26, большинство крипто-API просто игнорируют параметр io.Reader и используют внутренний системный источник случайных чисел.

Теперь можно передавать nil или ридер — это больше не имеет значения:

ecdsa.GenerateKey(elliptic.P256(), nil)


Кажется, это первый раз, когда в стандартной библиотеке Go несколько публичных API начали так себя вести. Это оправдано, но все равно как-то грустно.

Читать полностью…

Thank Go!

Go 1.26: Рекурсивные дженерики

В Go объявление дженерика содержит параметр типа и ограничение. Например:

type Foo[T any] interface {}
type Map[K comparable, V any] {}


Здесь Foo и Map — дженерики; T, K, V — параметры типа; comparable и any — ограничения.

Раньше ограничение не могло напрямую или косвенно ссылаться на дженерик. Например, вот такое объявление интерфейса:

// Значение, которое можно сравнивать с другими
// значениями того же типа с помощью операции "меньше".
type Ordered[T Ordered[T]] interface {
Less(T) bool
}


вызывало ошибку компиляции: "invalid recursive type: Ordered refers to itself".

В Go 1.26 все компилируется нормально. Теперь ограничения могут быть рекурсивными.

Рекурсивное ограничение может пригодиться, если дженерик должен работать с аргументами или результатами того же типа, что он сам (как Ordered[T] в примере выше).

Такой дженерик можно использовать как элемент в контейнере:

// Дерево, которое хранит ordered-значения.
type Tree[T Ordered[T]] struct {
nodes []T
}

// У netip.Addr есть метод Less с нужной сигнатурой,
// поэтому он подходит для Ordered[netip.Addr].
t := Tree[netip.Addr]{}


Так что дженерики в Go стали чуть более гибкими.

P.S. Правда, пример с деревом будет прекрасно работать и без всякой рекурсии 🤷‍♀️

Читать полностью…

Thank Go!

Как выделить 33 байта

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

К счастью, замешательство продолжалось недолго. Сейчас разберемся.

В Go аллокатор (штука, которая выделяет память) работает с фиксированными блоками памяти (так называемые size classes) для объектов меньше 32KB. Всего таких size-класса 67 штук: 8B, 16B, 24B, 32B, 48B, ..., 28672B, 32768B.

Если вы создаете срез емкостью (capacity) 33 байта:

s := make([]byte, 0, 33)


То аллокатор проставляет срезу значение cap = 33, но на самом деле выделяет не 33 байта, а подходящий ближайший size-класс — в данном случае 48B.

Если же вы создаете срез через append-make:

s := append([]byte(nil), make([]byte, 33)...)[:0]


То аллокатор точно так же выделяет 48B, а вот значение cap будет не 33, а тоже 48 (равно размеру size-класса).

Так как же заставить аллокатор выделить ровно 33 байта?

А никак 😁

Читать полностью…

Thank Go!

Интерактивный тур по Go 1.26

Опубликовал традиционный тур по будущему релизу (на англ). Часть фич мы с вами уже разобрали, а часть еще разберем, но если хотите прочитать все вместе уже сейчас — добро пожаловать.

Вот что вошло:

— new(expr)
— Безопасная проверка ошибок
— Новый «чайный» GC
— Ускоренный cgo и выделение памяти
— SIMD для amd64
— Секретный режим
— Криптография без ридеров
— Профиль для ловли утекающих горутин
— Метрики состояния горутин
— Итераторы в reflect
— Подсматривание в байтовый буфер
— Дескриптор процесса ОС
— Сигнал как причина в контексте
— Сравнение IP-подсетей
— Dialer с контекстом
— Фальшивый example.com
— Оптимизированные fmt.Errorf и io.ReadAll
— Множественные хендлеры в логах
— Артефакты тестов
— Обновленный go fix

Это жесть сколько всего они в релиз запихнули 😅

https://antonz.org/go-1-26

Читать полностью…

Thank Go!

Go 1.26: Быстрое выделение памяти

В Go появились специализированные версии функции выделения памяти (allocation) для маленьких объектов (от 1 до 512 байт).

Вместо одной универсальной функции теперь выбирается конкретная реализация, в зависимости от размера объекта.

В заметках к релизу Go написано: "the compiler will now generate calls to size-specialized memory allocation routines".

Но насколько я могу судить, это не совсем так. Компилятор все еще генерирует вызов общей функции mallocgc, а та уже в рантайме перенаправляет вызов в подходящую специализированную функцию.

Для маленьких объектов это изменение уменьшает затраты на выделение памяти до 30%. Команда Go ожидает, что в реальных программах с большим количеством аллокаций общее улучшение будет около 1%.

коммит

Читать полностью…

Thank Go!

Утечки горутин в Go 1.24+

Вы конечно и так в курсе, но на всякий случай:

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

Утрированный пример утечки:

func work() chan int {
ch := make(chan int)
go func() {
ch <- 42
ch <- 13 // (!) утечка
}()
return ch
}

func main() {
<-work()
// ...
}


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

Сейчас это меняется.

Сначала в Go 1.24 добавили пакет synctest, который прекрасно справляется с поиском утечек при тестировании. Об этом почему-то никто не говорит — наверно, потому что не проходили мой курс по многозадачности 😁

func Test(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
<-work()
synctest.Wait()
})
}


panic: main bubble goroutine has exited but blocked goroutines remain

goroutine 37 [chan send (durable), synctest bubble 1]:
main.work.func1()
leak_test.go:12 +0x3c
created by main.work in goroutine 36
leak_test.go:10 +0x6c


Как видите, тест и ругнулся, и точно указал, где возникла утечка.

А теперь в Go 1.26 подвезут новый pprof-профиль под названием goroutineleak — он надежно (без ложных срабатываний) обнаруживает утечки в продакшене (как вы понимаете, synctest-то в продакшене не запустишь).

func main() {
prof := pprof.Lookup("goroutineleak")

defer func() {
time.Sleep(50 * time.Millisecond)
prof.WriteTo(os.Stdout, 2)
}()

<-work()
}


goroutine 3 [chan send (leaked)]:
main.work.func1()
leak.go:12 +0x3c
created by main.work in goroutine 1
leak.go:10 +0x74


Как видите, указал ровно на ту же утечку, что synctest.

В общем, рекомендую оба инструмента. За подробностями велкам в блог:

https://antonz.org/detecting-goroutine-leaks

Читать полностью…

Thank Go!

map[T]struct{}

В отличие от питона и джавы, в го нет «родного» типа «множество» (set). Поэтому разработчики наловчились использовать вместо него map[T]struct{}:

set := make(map[string]struct{})
set["apple"] = struct{}{}
if _, exists := set["apple"]; exists {
fmt.Println("✓ apple")
}


✓ apple


Прелесть использования struct{} в том, что у такой карты значения буквально занимают 0 байт.

Но в Go 1.24 пришли швейцарские карты и случился потряс устоев.

map[T]struct{} теперь занимает ровно столько же памяти, сколько и map[T]bool. На значениях больше не сэкономить.

Дело в том, что в старых картах значения хранились отдельно от ключей: в каждом бакете сначала все ключи, потом все значения:

[K1, K2... K8] [V1, V2... V8]


Компилятор видел struct{}, и не выделял под значения память.

В новых картах ключи и значения объединены в одну структуру (слот):

[ {K1, V1}, {K2, V2}, ... ]


Внутри слот выглядит так:

struct {
key keyType
elem elemType
}


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

Таким образом, поле elem типа struct{} будет занимать 1 байт — точно так же, как bool. А весь слот — аж 16 байт, из-за паддинга (если ключ 8 байт).

Прощай, микро-оптимизация.

P.S. Прочитал я это у Артема Голубина, за что ему большое спасибо.

P.P.S. Разработчики в курсе проблемы, но в 1.26 фикс не войдет.

Читать полностью…

Thank Go!

Go 1.26: метрики горутин

Пакет runtime/metrics и раньше предоставлял вагон полезной информации о состоянии рантайма.

Но вот про горутины там было очень скромно: единственный показатель /sched/goroutines:goroutines — общее количество живых горутин.

Поэтому решили досыпать горутинных метрик:

— Общее количество горутин с начала работы программы.
— Количество горутин в каждом состоянии (ожидают, работают, ушли в системные вызовы).
— Количество активных потоков.

Такие метрики помогут быстрее замечать типичные проблемы в продакшене. Например:

→ Растет число горутин в состоянии waiting — может указывать на проблемы с блокировками.

→ Много горутин в состоянии not-in-go — они застревают в системных вызовах или cgo.

→ Увеличивается очередь runnable — CPU не справляются с нагрузкой.

Полный список новых метрик и примерчик смотрите на сайте:

https://antonz.ru/accepted/goroutine-metrics

Читать полностью…

Thank Go!

Go 1.26: Секретный режим

В новой версии Go добавили СЕКРЕТНЫЙ пакет. Никому о нем не рассказывайте! 🤫

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

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

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

Новый «секретный режим» в Go работает так. Запускаете свою функцию внутри secret.Do и делаете там разные грязные делишки: создаете сессионные ключи, шифры и все такое прочее. А когда функция отработает, рантайм стремительно очистит (затрет нулями) использованные регистры CPU и стековую память.

secret.Do(func() {
// Сгенерировть сессионный ключ
// и зашифровать им данные.
})


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

Пакет экспериментальный и разработчикам обычных приложений вряд ли понадобится. Но все равно интересный!

https://antonz.ru/accepted/runtime-secret

Читать полностью…

Thank Go!

Респектабельная швейцарская карта

Некоторые интервьюеры любят расспрашивать про устройство карты в Go. Если вы в ответ на такие вопросы привыкли заводить песню о хешировании и бакетах, в которых идет линейный перебор — то пора менять репертуар :)

Карта в Go 1.24+ перешла на открытую адресацию, но с более продвинутым и эффективным методом пробирования, основанным на контрольных словах. Алгоритм использует контрольные байты и SIMD-инструкции процессора, чтобы моментально определить подходящее место для ключа и избежать длительного перебора.

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

Была проблема в «классической» реализации swiss-таблиц — непредсказуемо большое время на рост таблицы, когда срабатывает порог по заполненности, и приходится копировать весь массив под таблицей и перехешировать в нем ключи. В Go обошли это, добавив «мета-таблицу», состоящую из множества маленьких таблиц с независимым ростом.

Такое решение, в свою очередь, породило сложности с итерацией по карте, если в процессе обхода она еще и модифицируется. Но и это тоже решили.

Если все это добро вам интересно, рекомендую короткое (всего полчаса) и очень внятное видео от Майкла Пратта, одного из авторов новой карты в Go.

Faster Go Maps With Swiss Tables

Читать полностью…

Thank Go!

Go 1.26: Безопасная проверка ошибок

Новая функция errors.AsType — это generic-версия errors.As. Она безопасная (type-safe), работает быстрее и проще в использовании.

Сравните errors.As:

var appErr AppError
if errors.As(err, &appErr) {
fmt.Println("Got an AppError:", appErr)
}


И errors.AsType:

if appErr, ok := errors.AsType[AppError](err); ok {
fmt.Println("Got an AppError:", appErr)
}


Преимущества AsType:

— Не нужно заранее объявлять переменную.
— Не использует рефлексию.
— Нет паники во время выполнения.
— Меньше аллокаций памяти.
— Проверка типов на этапе компиляции.
— Работает быстрее.

errors.As не объявлена устаревшей (пока), но для нового кода рекомендуется использовать errors.AsType.

Большое дело!

https://antonz.ru/accepted/errors-astype

Читать полностью…

Thank Go!

Go 1.26: Хешеры

В Go есть универсальная коллекция, основанная на хешировании — это карта (для быстрого доступа ключи карты хешируются и превращаются в индекс массива). Хеширование там под капотом, так что задумываться о нем не приходится. Обычно все так и делают.

Но иногда приходится разрабатывать карту, множество, или другую коллекцию «с нуля» — и тут от хеширования никуда не деться. В Go есть пакет maphash с полезными утилитами, но не хватало стандартного подхода к хешам в коллекциях.

Новый интерфейс maphash.Hasher — это стандартный способ хешировать и сравнивать элементы в пользовательских коллекциях:

// Hasher реализует хеширование
// и проверку на равенство для типа T.
type Hasher[T any] interface {
Hash(hash *maphash.Hash, value T)
Equal(T, T) bool
}


Кроме того, добавили тип ComparableHasher — это стандартная реализация хешера для comparable-типов (числа, строки, структуры с comparable-полями).

Нормальный пример использования в пост не умещается, так что если интересно — смотрите на сайте:
https://antonz.ru/accepted/maphash-hasher

Читать полностью…

Thank Go!

time.Sleep в synctest-тесте

Есть ну очень простой тест:

func Test(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
var done atomic.Bool
go func() {
time.Sleep(3*time.Second)
done.Store(true)
}()

if !done.Load() {
t.Error("done=false")
}
})
}


Что же он выдаст?

Если вы где-то слышали, что synctest.Test оперирует фальшивыми часами, которые автоматически идут вперед, то можете подумать, что time.Sleep в горутине выполнится мгновенно, а done=true установится до проверки в if.

На деле все сложнее. Часы идут вперед, только если все горутины устойчиво заблокированы. Здесь горутины две (основная и внутренняя), а заблокирована только внутренняя (на time.Sleep).

Поэтому основная горутина продолжит выполняться, проверка не пройдет, и тест напишет про done=false.

Но это еще не все.

Если при завершении synctest.Test видит, что есть заблокированные горутины — она паникует. Поэтому будет еще и паника:

main bubble goroutine has exited but blocked goroutines remain


Такие дела.

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

Читать полностью…

Thank Go!

Тестирование многозадачности

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

Но, несмотря на внешнюю безобидность (всего 2 функции!), synctest довольно непростой зверь.

Поэтому я написал по нему здоровенный урок на курсе по многозадачности. Из него вы узнаете:

— Как дождаться завершения горутины и проверить утечки.
— Что такое устойчивая блокировка и как с ней жить.
— Как использовать мгновенное ожидание.
— И вообще все о жизни внутри «пузыря» synctest.

В ближайшие пять дней урок открыт для всех, так что если хотите прокачать навыки — приглашаю пройти.

https://stepik.org/lesson/2051287

Читать полностью…

Thank Go!

Go — №2 среди новых языков

Посмотрел всякие рейтинги распространенности языков программирования. Если брать только условно-новые языки (у кого версия 1.0 вышла после 2010 года), то топ-6 выглядит так:

➀ TypeScript
➁ Go
➂ Rust
➃ Kotlin
➄ Dart
➅ Swift

Неплохой результат!

IEEE, SO, Languish

Читать полностью…

Thank Go!

Go 1.26: SIMD для amd64

В Go 1.26 наконец-то появились векторные операции (SIMD) в пакете simd/archsimd.

SIMD (single instruction, multiple data) — это когда одна инструкция процессора обрабатывает не одно число, а сразу несколько (вектор фиксированного размера). SIMD поддерживаются современными CPU разной архитектуры, но реализация у них отличается.

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

Думаю, это отличная идея — дать разработчикам попробовать новые возможности и собрать отзывы, прежде чем делать высокоуровневый интерфейс.

На скриншоте пример использования archsimd для сложения двух векторов произвольного размера.

Читать полностью…

Thank Go!

Ускоренная fmt.Errorf

В Go 1.26 много улучшений производительности, но мне особенно понравилась оптимизированная fmt.Errorf — небольшое, но приятное дополнение.

Как вы знаете, errors.New("x") и fmt.Errorf("x") создают одинаковую ошибку типа errorString. Поэтому проще и единообразнее всегда использовать fmt.Errorf.

Проблема была в том, что fmt.Errorf работала в 3 раза медленнее, чем errors.New.

Один из разработчиков Go устал слышать, что "fmt.Errorf медленная", и решил это исправить. Он добавил быстрый путь, чтобы для "неформатированных" ошибок не запускалась вся тяжелая логика, а сразу вызывалась errors.New (см. скриншот).

Теперь fmt.Errorf("x") выполняет только одну аллокацию (как и errors.New) и всего на 20% медленнее (25нс против 21нс).

Очень круто!

коммит

Читать полностью…

Thank Go!

Прожорливый make и скромный append

Читатель заметил интересный момент в новой реализации io.ReadAll. Там собирается финальный срез байт из кусочков, а перед этим для него выделяется память, вот так:

final := append([]byte(nil), make([]byte, finalSize)...)[:0]


Так вот, вопрос — зачем такие сложности? Почему не сделать просто так:

final := make([]byte, 0, finalSize)


UPD. Я не знаю 😅 Изначально я неправильно понял поведение компилятора, и написал, что append-make выделяет ровно finalSize байт, в отличие от make. Но это не так — оба варианта аллоцируют одинаково.

А вот что точно известно, так это что такое выражение:

append([]T(nil), make([]T, n)...)


не приводит к двойной аллокации (сначала для make, потом для append), как можно подумать по его внешнему виду.

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

Поведение это публично не документировано. Но разработчики стдлибы о нем знают и пользуются 😈

Читать полностью…

Thank Go!

Go 1.26: Ускоренный cgo

Слоганом Go 1.26 можно делать фразу «ускорилось ВСЕ». Помимо GreenTea GC и скоростных аллокаций (о которых я писал выше) — теперь еще и cgo-вызовы работают на 20–30% быстрее.

Вот немного подробностей.

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

Процессоры могут находиться в разных состояниях: running (выполняет код), idle (ждет работы) или gcstop (остановлен из-за сборки мусора).

Раньше было еще состояние syscall, когда горутина делала системный или cgo-вызов. Теперь это состояние убрали.

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

Это уменьшает накладные расходы и упрощает работу с cgo и системными вызовами.

Красота!

Читать полностью…

Thank Go!

Go 1.26: Обновленный go fix

С годами команда "go fix" превратилась в свалку покрытых плесенью рефакторингов, которые никто не использует.

Сейчас это изменится.

В 1.26 go fix переписана с нуля и использует тот же движок, что go vet — но другой набор анализаторов.

В отличие от go vet, исправления go fix безопасны (можно применять автоматически) и нацелены больше на модернизацию кода под новые фичи языка и стдлибы, а не исправление проблем в коде.

Пример: замена циклов на slices.Contains:

// до go fix
func find(s []int, x int) bool {
for _, v := range s {
if x == v {
return true
}
}
return false
}


// после go fix
func find(s []int, x int) bool {
return slices.Contains(s, x)
}


Сейчас в go fix больше 20 исправлений. Подробности в блоге:

https://antonz.ru/accepted/modernized-go-fix

Читать полностью…

Thank Go!

map[T]struct{}: новая надежда

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

Итак, map[T]struct{} стала занимать столько же, сколько map[T]bool. И это не +1 байт, как может показаться.

Данные в новой карте хранятся в группах:

type group struct {
ctrl uint64
slots [8]slot
}

type slot struct {
key K
elem E
}


Если elem занимает один байт, это увеличивает размер слота не на 1 байт, а более значительно — из-за необходимости выравнивать структуру.

Например, для ключа в 8 байт слот займет 16 байт, а для ключа в 16 байт — 24 байта.

Если вы поклонник громадных, необъятных карт со struct{}, это заметная проблема.

Команда Go рассматривает два варианта решения.

➊ Переделать слоты в группе на KVKVKVKV

В слоте ключ и значение меняются местами:

type slot struct {
elem E
key K
}


Поскольку для struct{} пустое поле elem идет не последним, оно занимает 0 байт. Соответственно, слот занимает ровно столько, сколько занимает ключ.

➋ Переделать слоты в группе на KKKKVVVV

Реинкарнация прежнего карточного подхода — ключи отдельно, значения отдельно:

type group struct {
ctrl uint64
keys [8]K
elems [8]V
}


Посколько для struct{} массив elems содержит только нулевые элементы, весь массив занимает 0 байт. Потерь тоже нет.

Оба варианта уже реализованы, но ни один не замержен.

Болеем за карту 🤞

Читать полностью…

Thank Go!

Тайминги для Hello world

На приложенной картинке — глубоко ненаучный бенчмарк по компиляции и выполнению примитивной hello-world программы на разных языках.

Каких-то глубоких выводов из нее не сделаешь, просто любопытно :)

Что касается Go, то компиляция в нем работает достаточно быстро даже на проектах с большим количеством зависимостей — потому что система сборки кеширует собранные зависимости (как стдлибу, так и third-party).

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

В общем, Go в плане компиляции молодец!

Читать полностью…

Thank Go!

Контекст с отменой, но без отмены

Go-разработчики делятся на три группы:

1. Используют контекст для отмены.
2. Используют контекст для передачи значений.
3. Используют контекст для отмены и значений.

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

Дело в том, что дочерний контекст отменяется при отмене родительского:

ctx := context.WithValue(context.Background(), "id", 42)
parentCtx, cancel := context.WithCancel(ctx)
childCtx := parentCtx
cancel()

fmt.Printf("child canceled = %v\n", childCtx.Err() != nil)
fmt.Printf("child id = %v\n", childCtx.Value("id"))


child canceled = true
child id = 42


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

ctx := context.WithValue(context.Background(), "id", 42)
parentCtx, cancel := context.WithCancel(ctx)
childCtx := context.Background()
cancel()

fmt.Printf("child canceled = %v\n", childCtx.Err() != nil)
fmt.Printf("child id = %v\n", childCtx.Value("id"))


child canceled = false
child id = <nil>


И вот для поддержки такого, кхм, сценария, в Go 1.21 добавили функцию context.WithoutCancel, которая отцепляет дочерний контекст от родительского в смысле отмены, но сохраняет все значения:

ctx := context.WithValue(context.Background(), "id", 42)
parentCtx, cancel := context.WithCancel(ctx)
childCtx := context.WithoutCancel(parentCtx)
cancel()

fmt.Printf("child canceled = %v\n", childCtx.Err() != nil)
fmt.Printf("child id = %v\n", childCtx.Value("id"))


child canceled = false
child id = 42


Стало очень удобно. Ну, наверно 🤯

Читать полностью…

Thank Go!

Go 1.26: Dialer с контекстом

Пакет net помимо прочего отвечает за «дозвон» (dial) к сетевым адресам по разным протоколам — TCP, UDP, IP или Unix-сокеты.

В пакете есть отдельные функции для разных протоколов: DialTCP, DialUDP, DialIP и DialUnix. Но вот беда: их сделали до появления интерфейса Context, поэтому они не поддерживают отмену.

С другой стороны, есть тип-звонилка net.Dialer, а у него универсальный метод DialContext. Он поддерживает отмену и может использоваться для подключения по любому из поддерживаемых протоколов. Но вот беда: он более тормозной, чем отдельные функции (потому что универсальный).

В итоге получилось и то не то, и это не это.

Поэтому решили докинуть типу Dialer новых методов — чтобы и быстрые были, и отмену поддерживали. Теперь будут и функции net.DialШто и методы net.Dialer.DialШто.

Не перепутайте!

Шучу, вы их все равно использовать не будете 😁

https://antonz.ru/accepted/net-dialer-context

Читать полностью…

Thank Go!

Go 1.26: Сравнение IP-подсетей

Префикс IP-адреса задает определенную подсеть. Обычно такие префиксы записывают в формате CIDR:

10.0.0.0/8
127.0.0.0/8
169.254.0.0/16
203.0.113.0/24


В Go IP-префикс представлен типом netip.Prefix.

Когда команда Go изначально разрабатывала этот тип, они решили не добавлять метод Compare, потому что не было общепринятого способа упорядочивать такие IP-подсети.

Из-за этого, если разработчику нужно отсортировать подсети, приходится писать собственную логику сравнения.

Теперь в Go добавят стандартный способ сравнения IP-префиксов. Это должно уменьшить количество шаблонного кода и помочь всем сортировать IP-подсети одинаково.

Пример:

prefixes := []netip.Prefix{
netip.MustParsePrefix("10.1.0.0/16"),
netip.MustParsePrefix("203.0.113.0/24"),
netip.MustParsePrefix("10.0.0.0/16"),
netip.MustParsePrefix("169.254.0.0/16"),
netip.MustParsePrefix("203.0.113.0/8"),
}

slices.SortFunc(prefixes, func(a, b netip.Prefix) int {
return a.Compare(b)
})


10.0.0.0/16
10.1.0.0/16
169.254.0.0/16
203.0.113.0/8
203.0.113.0/24


Мелочь, но может пригодиться.

https://antonz.ru/accepted/netip-prefix-compare

Читать полностью…

Thank Go!

Знакомство с Go

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

В отличие от обычных курсов-знакомств, тут никто не рассказывает, что такое переменная и чем она отличается от цикла. Бестолковых задач вроде «что напечатает функция» тоже нет. Все кратко и по делу.

Освоить язык целиком по курсу не получится (для этого у меня есть другой курс, я же матерый инфоцыган препод), а вот понять «это вообще мое или нет» — запросто.

Так что если захотите сманить в го знакомого похапэра или питоняшу — можно смело рекомендовать :)

https://stepik.org/a/263524

P.S. Если вы записаны на курс по многозадачности — там на уроке про внутрянку добавилась теория про метрики, профилирование и трассировку. И задачка!

Читать полностью…

Thank Go!

Go 1.26: new(expr)

Наступила осень, а значит пора разбирать новые фичи, запланированные в Go 1.26 (но это вам на новый год!)

Начнем со встроенной функции (builtin) new, которую теперь можно использовать для любых выражений (кроме nil).

Раньше new можно было использовать только с типами:

p1 := new(int)
fmt.Println(*p1)
// 0


А теперь можно и так:

// Указатель на переменную типа int со значением 42.
p := new(42)
fmt.Println(*p)
// 42


Или даже так:

f := func() string { return "go" }
p := new(f())
fmt.Println(*p)
// go


Говоря сухим языком спецификации:

Если аргумент expr — это выражение типа T, то new(expr) аллоцирует переменную типа T (то есть выделяет под нее память), инициализирует ее значением expr и возвращает ее адрес, то есть значение типа *T.

подробности

Читать полностью…

Thank Go!

time.Sleep в тесте

Есть ну очень простой тест:

func Test(t *testing.T) {
synctest.Test(t, func(t *testing.T) {
var done atomic.Bool
go func() {
time.Sleep(3*time.Second)
done.Store(true)
}()

if !done.Load() {
t.Errorf("done=false")
}
})
}


Как думаете, что будет, если его выполнить через go test с параметром timeout=1s? Чур без спойлеров в комментариях.

Опрос следует.

Читать полностью…

Thank Go!

Факты о дорогих читателях

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

Некоторые факты от которых задергался глаз у редакции:

Джунов на Go только 10%. Начал писать на го — считай сразу сеньор.

Половина го-разработчиков изменяют с питоном.

94% разработчиков в офисе никто не ждет.

Если вы не разработали на Go хотя бы одну апишечку, то жизни не знаете.

Две трети разработчиков несчастливы одинаково: они используют кубернетис.

Половина разработчиков используют контрабандный GoLand.

3% работают в подвале без интернета, поэтому у них Go версии 1.20.

11 человек получают 500 тыщ рублей ежемесячно за свои труды.

Есть хорошие шансы, что вы знаете, кто такой Николай Тузов (я вот понятия не имею).

Полный отчет: https://devcrowd.ru/go-2025/

Читать полностью…

Thank Go!

Горутины и потоки

Go не дает программисту явно управлять потоками.

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

Но если очень хочется, можно прибить горутину к конкретному потоку через runtime.LockOSThread:

var wg sync.WaitGroup
wg.Add(nWorkers)
for range nWorkers {
go func() {
runtime.LockOSThread()
defer runtime.UnlockOSThread()
defer wg.Done()
// do work
}()
}
wg.Wait()


Так ➀ рантайм будет исполнять горутину только на том потоке, где она стартовала изначально, и ➁ другие горутины не будут использовать этот поток до вызова UnlockOSThread.

В большинстве случаев это не требуется. Рантайм прекрасно справляется с планированием горутин без явного контроля потоков.

Использовать LockOSThread обычно имеет смысл только при вызовах сишных библиотек, которые используют thread-local state (то есть могут сломаться, если горутина переедет на другой поток).

Если вам интересно, можно ли явно запускать гошный код в потоках и процессах ОС — да, можно. Но точно не нужно :) Рантайм на такие финты не рассчитан, и вероятность огрести проблем очень велика.

Читать полностью…
Subscribe to a channel