Горутина — это легковесный поток выполнения, управляемый рантаймом Go. Для запуска новой горутины используют ключевое слово go перед вызовом функции. Под капотом создается структура, описывающая стек и состояние задачи, которая добавляется в очередь планировщика горутин.
В отличие от потоков ОС, горутина имеет гораздо меньший начальный стек (обычно 2 КБ), и стек автоматически расширяется при необходимости. Планировщик Go сам распределяет их по доступным потокам операционной системы (M:N-модель).
Основные отличия от потоков (threads):
package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d started job %d ", id, j) time.Sleep(time.Second) fmt.Printf("worker %d finished job %d ", id, j) results <- j * 2 } } func main() { jobs := make(chan int, 5) results := make(chan int, 5) for w := 1; w <= 3; w++ { go worker(w, jobs, results) } for j := 1; j <= 5; j++ { jobs <- j } close(jobs) for a := 1; a <= 5; a++ { <-results } }
Могут ли горутины Go выполняться параллельно на нескольких ядрах?
Часто встречается неверный ответ: "Нет, потому что Go использует зеленые потоки". На самом деле, с помощью переменной окружения или вызова runtime.GOMAXPROCS(n) Go может распараллеливать выполнение горутин на всех доступных ядрах процессора.
import "runtime" func main() { runtime.GOMAXPROCS(4) // Позволяет использовать 4 ядра ... }
История
В проекте backend-сервиса на Go был реализован пул воркеров через горутины, но программисты забыли ограничить количество одновременно работающих горутин. В результате при увеличении нагрузки приложение запускало тысячи горутин, что привело к исчерпанию памяти и краху сервиса. Проблема решилась введением лимита активных горутин (например, с помощью семафора или worker pool).
История
Один из сотрудников неверно синхронизировал данные между горутинами, используя обычные глобальные переменные без мьютексов или каналов. Это вызвало гонку данных (race condition), из-за чего периодически происходили ошибки при обработке платежей. Проблема была обнаружена только после запуска в production.
История
В сервисе парсинга был упущен момент передачи nil-каналов в select: после закрытия канала select продолжал заблокированно ждать данные, и часть горутин "повисала". Исправили присваиванием nil закрытому каналу и правильной обработкой select.