Imeundwa na AIImeboreshwa na watu
Riqli · hati hai · zinasasishwa kila mara
Mahali maarifa ya AI yanakutana na mazoezi ya binadamu.
Shiriki na marafiki

Конкурентность в Go (goroutines, channels, context)
Введение в конкурентность Go
Аннотация. Данный модуль посвящён ключевым примитивам конкурентного программирования в языке Go: горутинам, каналам и пакету context. Конкурентность — это одна изdefining особенностей Go, позволяющая эффективно использовать многоядерные процессоры и создавать масштабируемые приложения. В отличие от традиционных потоков операционной системы, горутины управляются средой выполнения Go и обладают значительно меньшими накладными расходами.
Цель. Сформировать практические навыки написания безопасных и эффективных конкурентных программ на Go с использованием горутин, каналов и контекста для управления временем жизни операций.
Результаты обучения. После изучения модуля вы сможете: запускать горутины с помощью ключевого слова go; создавать небуферизованные и буферизованные каналы; использовать select для мультиплексирования операций; применять sync.WaitGroup для ожидания завершения группы горутин; управлять отменой и таймаутами через context.WithCancel, WithTimeout и WithDeadline; избегать утечек горутин и взаимных блокировок.
Целевая аудитория. Разработчики, знакомые с основами синтаксиса Go и желающие освоить конкурентное программирование для создания производительных серверных приложений и инструментов.
Горутины: лёгкие потоки выполнения
Что такое горутина. Горутина — это функция, выполняющаяся конкурентно с вызывающей её функцией. Для запуска горутины используется ключевое слово go перед вызовом функции или анонимной функции. Например, go worker(id, jobs) запускает функцию worker в новой горутине. Аргументы вычисляются в текущей горутине, а выполнение worker происходит в новой .
Лёгкость горутин. Горутины начинаются с минимального стека размером около 2 КБ, который динамически растёт по мере необходимости. Для сравнения, системные потоки обычно требуют 1 МБ начального стека. Управление горутинами осуществляется средой выполнения Go, а не операционной системой, что обеспечивает быстрое переключение контекста и позволяет запускать сотни тысяч горутин на одном сервере без чрезмерного потребления памяти .
Планирование. Go использует модель планирования GMP (Goroutine, M — поток ОС, P — логический процессор). Планировщик распределяет горутины по доступным потокам ОС. Если горутина выполняет блокирующий системный вызов, среда выполнения автоматически переключает другие горутины на другие потоки ОС, чтобы они не простаивали .
Завершение горутин. Горутина завершается естественным образом, когда её функция возвращает управление. Однако если горутина запущена как долгоживущий фоновый процесс, необходимо предусмотреть механизм её остановки. Неуправляемые горутины, ожидающие данные из канала, который никогда не будет закрыт, приводят к утечке памяти .
Какое ключевое слово используется для запуска функции в новой горутине?
go
Ключевое слово go перед вызовом функции запускает её в новой горутине, которая выполняется конкурентно с вызывающей .
async
thread
concurrent
Какой начальный размер стека у горутины в Go?
Около 2 КБ
Горутины начинаются с минимального стека около 2 КБ, который растёт динамически по мере необходимости .
1 МБ
8 КБ
64 КБ
Каналы: коммуникация между горутинами
Назначение каналов. Каналы — это типизированные конвейеры, через которые горутины обмениваются значениями. Философия Go: «Не общайтесь, разделяя память; вместо этого делитесь памятью, общаясь» . Канал создаётся с помощью make(chan T) для небуферизованного или make(chan T, N) для буферизованного канала .
Операции отправки и получения. Отправка значения в канал: ch <- v. Получение значения: v := <-ch. Данные текут в направлении стрелки. Небуферизованный канал блокирует отправителя до тех пор, пока получатель не будет готов принять значение, и наоборот .
Буферизованные каналы. Буферизованный канал имеет ёмкость. Отправка блокируется только когда буфер заполнен. Получение блокируется только когда буфер пуст. Буферизация позволяет отправителю опережать получателя на N элементов и используется для сглаживания пиковых нагрузок, но не должна заменять продуманное проектирование потоков данных .
Закрытие каналов. Отправитель может закрыть канал вызовом close(ch), сигнализируя, что больше значений не будет. Получатели могут проверять закрытие через двухзначную форму: v, ok := <-ch. Если ok == false, канал закрыт и пуст. Цикл for v := range ch автоматически завершается при закрытии канала. Закрывать канал должен только отправитель. Отправка в закрытый канал вызывает панику .
Направленные каналы. Параметры каналов могут быть ограничены только отправкой (chan<- T) или только получением (<-chan T), что документирует намерения и проверяется компилятором .
Что произойдёт при отправке значения в закрытый канал?
Возникнет паника (panic)
Отправка в закрытый канал вызывает панику, поэтому закрывать канал должен только отправитель и только один раз .
Значение будет проигнорировано
Канал автоматически откроется снова
Получатель получит нулевое значение
Какой оператор используется для отправки значения 42 в канал ch?
ch <- 42
Оператор <- направляет значение в канал: ch <- 42 отправляет 42 в канал ch .
42 -> ch
send(ch, 42)
ch = 42
Select: мультиплексирование каналов
Назначение select. Оператор select позволяет горутине ожидать одновременно несколько операций с каналами. Он блокируется до тех пор, пока один из его case-блоков не станет готов к выполнению, после чего выполняет этот case. Если готовы несколько case-блоков, выбирается случайный, что обеспечивает справедливость .
Синтаксис. select похож на switch, но вместо значений проверяет готовность операций с каналами. Каждый case содержит операцию отправки или получения. Например: select { case v := <-ch1: ... case ch2 <- x: ... case <-time.After(time.Second): ... } .
Default. Ветка default выполняется, если ни один из case-блоков не готов. Это позволяет реализовать неблокирующие операции try-send и try-receive. Без default оператор select блокируется, пока хотя бы один канал не будет готов .
Практические сценарии. Select используется для: таймаутов (через time.After), отмены операций (через <-ctx.Done()), неблокирующей отправки или получения, а также для ожидания нескольких источников данных одновременно .
Какая ветка select выполняется, если ни один из case-блоков не готов?
default
Ветка default в select выполняется, когда ни один из case-блоков не готов к выполнению, что делает select неблокирующим .
Первый case по порядку
Последний case по порядку
Возникнет паника
Синхронизация с sync.WaitGroup
Назначение WaitGroup. Пакет sync предоставляет примитив WaitGroup для ожидания завершения группы горутин. Нулевое значение WaitGroup готово к использованию без дополнительной инициализации .
Методы. Add(delta int) увеличивает счётчик на delta. Обычно вызывается перед запуском каждой горутины. Done() уменьшает счётчик на единицу. Эквивалентно Add(-1). Вызывается по завершении горутины, часто через defer. Wait() блокирует вызывающую горутину до тех пор, пока счётчик не станет равным нулю .
Пример использования. Типичный паттерн: var wg sync.WaitGroup, затем для каждой задачи wg.Add(1) и go func() { defer wg.Done(); ... }(). После запуска всех горутин вызывается wg.Wait(), который блокирует основную горутину до завершения всех задач. Этот механизм особенно полезен, когда главная горутина должна дождаться завершения рабочих горутин, прежде чем продолжить .
Какой метод sync.WaitGroup уменьшает счётчик на единицу?
Done()
Метод Done() уменьшает счётчик WaitGroup на единицу и обычно вызывается через defer при завершении горутины .
Wait()
Add(-1)
Close()
Пакет context: управление временем жизни
Назначение context. Пакет context предоставляет механизм передачи сигналов отмены, дедлайнов и таймаутов через границы API и между горутинами. Контекст позволяет управлять временем жизни операций и предотвращать утечки горутин .
Создание контекста. context.Background() возвращает пустой корневой контекст, обычно используемый в main или на верхнем уровне. context.TODO() используется, когда родительский контекст ещё не определён, но в будущем должен быть добавлен .
WithCancel. context.WithCancel(parent) возвращает производный контекст и функцию cancel. Вызов cancel() закрывает канал Done(), сигнализируя всем горутинам, слушающим этот контекст, о необходимости завершиться. Отмена освобождает ресурсы, поэтому cancel следует вызывать как можно скорее после завершения операции .
WithTimeout и WithDeadline. context.WithTimeout(parent, duration) создаёт контекст, который автоматически отменяется через указанный интервал. context.WithDeadline(parent, time) устанавливает абсолютное время дедлайна. Оба возвращают функцию cancel для досрочной отмены .
Паттерн использования. Контекст передаётся первым аргументом в функции, выполняющие потенциально длительные операции. Горутина периодически проверяет <-ctx.Done() в select и завершается при получении сигнала. Это позволяет реализовать graceful shutdown и таймауты для сетевых запросов, операций ввода-вывода и фоновых задач .
Какая функция пакета context возвращает производный контекст с автоматической отменой через заданный интервал времени?
context.WithTimeout