Go: ОС и сети

15 вопросов

1 Процесс и поток (thread) в ОС. Как это соотносится с Go.

Процесс - изолированный экземпляр программы с собственным адресным пространством. Поток - единица выполнения внутри процесса; потоки делят память процесса. В Go одна программа - один процесс; множество горутин планируются на несколько потоков ОС (M:N). GOMAXPROCS задает макс. число потоков для пользовательского кода. Горутины легче потоков ОС.

runtime.GOMAXPROCS(runtime.NumCPU())
Открыть отдельно →
2 Context switching. Влияние на горутины в Go.

Переключение контекста - сохранение состояния потока и загрузка другого. Частые переключения - накладные расходы. В Go переключение между горутинами дешевле переключения потоков ОС: планировщик Go переключает горутины на одних и тех же потоках. Блокирующий syscall (например, чтение с диска) может занять поток ОС; тогда создается новый поток для других горутин. Не блокировать горутины долгими syscall без ограничения их числа.

Открыть отдельно →
3 Сигналы в Go. Обработка SIGTERM, SIGINT.

Сигналы ОС можно перехватить через signal.Notify. SIGINT (Ctrl+C) и SIGTERM (kube stop) - типичные для graceful shutdown. В Go перехватывают в канал, в select с контекстом или каналом остановки; при получении отменяют контекст и завершают сервер (Shutdown), закрывают соединения.

sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
<-sigCh
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
srv.Shutdown(ctx)
Открыть отдельно →
4 IPC (межпроцессное взаимодействие) в Go.

Способы: пайпы (os.Pipe), сокеты (Unix domain: net.Listen("unix", path)), разделяемая память (редко в Go), очереди сообщений. В Go чаще сокеты (TCP или unix) и HTTP/gRPC поверх них. Для локального взаимодействия unix socket быстрее TCP. Пример: сервис слушает unix socket, другой процесс подключается через net.Dial("unix", path).

ln, _ := net.Listen("unix", "/tmp/app.sock")
conn, _ := ln.Accept()
Открыть отдельно →
5 File descriptors в Go. Лимиты и утечки.

Каждое сетевое соединение, открытый файл - файловый дескриптор. Лимит процесса - ulimit -n. В Go при утечке (не закрытые соединения, не закрытые файлы) дескрипторы заканчиваются. Всегда закрывать: defer conn.Close(), defer file.Close(), после использования response body: defer resp.Body.Close(). При пуле соединений контролировать размер пула и закрытие при shutdown.

f, err := os.Open(path)
if err != nil { return err }
defer f.Close()
Открыть отдельно →
6 Стек и куча в Go. Escape analysis.

Локальные переменные обычно на стеке; при "уходе" за пределы функции (возврат указателя, замыкание, сохранение в глобальной переменной) компилятор делает escape - выделение на куче. Escape analysis: go build -gcflags="-m" показывает, что уходит на кучу. Цель - минимизировать аллокации на куче для снижения нагрузки на GC. Указатели на стеке не должны сохраняться после возврата.

// escape: return &x
// no escape: return x
Открыть отдельно →
7 Виртуальная память и страницы. Связь с Go.

Виртуальная память - каждому процессу свое адресное пространство; страницы маппятся на физическую память или swap. В Go приложение использует виртуальную память через runtime: куча горутин, стеки, куча аллокаций. Рост потребления - куча и стеки. OOM Killer убивает процесс при нехватке памяти; лимиты в контейнерах (cgroup) ограничивают использование.

Открыть отдельно →
8 Endianness и бинарные протоколы в Go.

Порядок байт: little-endian (младший байт по младшему адресу, x86) и big-endian (сетевой порядок). В Go encoding/binary: binary.BigEndian.PutUint32, binary.LittleEndian.Uint32; при чтении из сети обычно big-endian. Пакет binary.Read/binary.Write с порядком. Важно при реализации бинарных протоколов и чтении форматов файлов.

var val uint32
binary.Read(r, binary.BigEndian, &val)
buf := make([]byte, 4)
binary.BigEndian.PutUint32(buf, val)
Открыть отдельно →
9 Copy-on-Write (COW) в ОС и приложениях.

COW - копирование при записи: общие данные до изменения, при записи создается копия. В ОС - fork процесса (страницы общие до записи). В приложениях - неизменяемые структуры, при "изменении" создается копия с изменением. В Go копирование слайсов и мап не COW; для иммутабельных данных делают копии при обновлении или используют структуры с версионированием.

Открыть отдельно →
10 epoll и kqueue. Как Go использует асинхронный I/O.

epoll (Linux) и kqueue (BSD/macOS) - механизмы мультиплексирования I/O: один поток ждет события на множестве дескрипторов. В Go netpoller абстрагирует это: горутина блокируется на Read/Write, runtime регистрирует дескриптор в netpoller и переключает горутину на другую; при готовности I/O горутина возобновляется. Пользовательский код синхронный (блокирующий вызов), под капотом асинхронность.

Открыть отдельно →
11 Сокеты в Go. TCP и UDP API.

net.Listen("tcp", ":8080") - слушать TCP; Accept() возвращает net.Conn (Read, Write, Close). net.Dial("tcp", "host:port") - клиентское соединение. UDP: net.ListenPacket("udp", ":8080"), ReadFrom/WriteTo (без установленного соединения). В Go сокеты абстрагированы через net.Conn и net.PacketConn; таймауты через SetDeadline. HTTP и gRPC строятся поверх TCP.

ln, _ := net.Listen("tcp", ":8080")
conn, _ := ln.Accept()
defer conn.Close()
io.Copy(conn, conn)
Открыть отдельно →
12 Зеленые потоки (горутины) и планировщик Go.

Горутины - пользовательские легкие потоки (зеленые); планировщик Go (runtime) распределяет их по потокам ОС (M). G - горутина, M - поток ОС, P - контекст процессора (до GOMAXPROCS). При блокировке горутины (канал, syscall) M может отвязываться и выполнять другую горутину. Старт горутины дешевый (несколько КБ стека, растет при необходимости).

go func() { ... }()
Открыть отдельно →
13 CPU steal time в виртуализации. Влияние на Go.

Steal time - время, когда виртуальный CPU готов выполнять код, но гипервизор отдал квант другому виртуал-хосту. Высокий steal - контейнер/ВМ недополучает CPU. В Go приложение видит это как замедление; метрики (например, node_cpu_seconds_total с mode=steal в Prometheus) помогают диагностировать. Решение - на уровне инфраструктуры (выделенные ядра, другой хост).

Открыть отдельно →
14 cgroups и namespaces в Linux. Go в контейнерах.

cgroups ограничивают ресурсы (CPU, память, I/O); namespaces изолируют видимость (PID, сеть, mount). Контейнер - процесс(ы) в своих namespaces с лимитами cgroups. Go-приложение в контейнере видит лимиты через /sys/fs/cgroup; GOMEMLIMIT можно выставить под cgroup memory limit. runtime.NumCPU() учитывает лимиты CPU; дефолтный размер стека горутины не зависит от cgroup.

// чтение лимита памяти cgroup для GOMEMLIMIT
// /sys/fs/cgroup/memory/memory.limit_in_bytes
Открыть отдельно →
15 OOM Killer и поведение Go при нехватке памяти.

При нехватке памяти ядро может убить процесс (OOM Killer). В Go при росте кучи GC пытается освободить память; при невозможности процесс может быть убит. В контейнерах лимит памяти задает cgroup; превышение ведет к OOM kill. GOMEMLIMIT (Go 1.19+) ограничивает кучу ниже лимита cgroup, чтобы GC активнее работал и снижал пиковое потребление, уменьшая риск OOM.

// GOMEMLIMIT=400Mi при лимите контейнера 512Mi
// оставляет запас для стека и прочего
Открыть отдельно →
🧠Квиз 🏆Лидеры 🎯Собесед. 📖Вопросы 📚База зн.