Ответ
В Go нет механизма принудительного завершения горутины извне. Вместо этого используется паттерн кооперативной отмены, когда горутина сама проверяет сигнал о необходимости завершения и корректно прекращает свою работу.
Существует два основных способа для реализации этого:
1. Использование сигнального канала
Это классический способ, при котором закрытие канала используется как широковещательный сигнал для всех горутин, которые его слушают.
// worker ожидает сигнала в канале stopCh
func worker(id int, wg *sync.WaitGroup, stopCh <-chan struct{}) {
defer wg.Done()
fmt.Printf("Воркер %d запущенn", id)
for {
select {
case <-stopCh:
// Получен сигнал на завершение
fmt.Printf("Воркер %d останавливаетсяn", id)
return
default:
// Выполнение полезной работы
fmt.Printf("Воркер %d работает...n", id)
time.Sleep(1 * time.Second)
}
}
}
func main() {
var wg sync.WaitGroup
stopCh := make(chan struct{})
for i := 1; i <= 3; i++ {
wg.Add(1)
go worker(i, &wg, stopCh)
}
// Даем воркерам поработать 3 секунды
time.Sleep(3 * time.Second)
// Отправляем сигнал на завершение всем воркерам
fmt.Println("Отправка сигнала на остановку...")
close(stopCh)
// Ожидаем, пока все воркеры корректно завершат работу
wg.Wait()
fmt.Println("Все воркеры остановлены.")
}
close(stopCh)
: Закрытие канала немедленно разблокирует все ожидающие операции чтения (<-stopCh
), которые получают нулевое значение. Это делает его идеальным для одновременной сигнализации множеству горутин.sync.WaitGroup
: Используется для того, чтобыmain
горутина дождалась фактического завершения всех дочерних горутин перед выходом из программы.
2. Использование context.Context
Это более современный и предпочтительный способ, особенно в сложных приложениях (например, при обработке HTTP-запросов), так как context
позволяет передавать сигналы отмены, таймауты и значения по всему стеку вызовов.
func workerWithContext(ctx context.Context, wg *sync.WaitGroup) {
defer wg.Done()
for {
select {
case <-ctx.Done():
// Контекст был отменен, завершаем работу
fmt.Println("Завершение работы через контекст...")
return
default:
// Полезная работа
fmt.Println("Работаю...")
time.Sleep(500 * time.Millisecond)
}
}
}
func main() {
ctx, cancel := context.WithCancel(context.Background())
var wg sync.WaitGroup
wg.Add(1)
go workerWithContext(ctx, &wg)
// Через 2 секунды отменяем контекст
time.Sleep(2 * time.Second)
cancel() // Эта функция отправляет сигнал в канал ctx.Done()
wg.Wait() // Ожидаем завершения горутины
fmt.Println("Горутина завершена.")
}
context.WithCancel
: Создает контекст, который можно отменить вручную вызовом функцииcancel()
.<-ctx.Done()
: КаналDone()
закрывается, когда вызываетсяcancel()
, что служит сигналом для завершения.