Как правилно да завършите изпълнението на няколко горутини в Go?
sobes.tech AI
Отговор от AI
Има няколко често срещани начина:
-
Използване на контекст (Context): Препоръчителният подход за анулиране на операции в дървовидна структура от извиквания. Контекстът може да пренася сигнал за анулиране чрез канал.
package main import ( "context" "fmt" "time" ) func worker(ctx context.Context, id int) { for { select { case <-ctx.Done(): fmt.Printf("Горутината %d приключва: %v\n", id, ctx.Err()) return default: fmt.Printf("Горутината %d работи...\n", id) time.Sleep(1 * time.Second) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) go worker(ctx, 1) go worker(ctx, 2) time.Sleep(3 * time.Second) cancel() // Изпращане на сигнал за анулиране time.Sleep(1 * time.Second) // Даваме време на горутините да приключат fmt.Println("Основната програма е приключила.") } -
Използване на канали: Изпращайки сигнал (например, празна структура
struct{}) по специален канал, може да уведомите горутината за необходимостта от приключване.package main import ( "fmt" "time" ) func worker(stopChan <-chan struct{}, id int) { for { select { case <-stopChan: fmt.Printf("Горутината %d приключва по канал\n", id) return default: fmt.Printf("Горутината %d работи...\n", id) time.Sleep(1 * time.Second) } } } func main() { stopChan := make(chan struct{}) go worker(stopChan, 1) go worker(stopChan, 2) time.Sleep(3 * time.Second) close(stopChan) // Затварянето на канала сигнализира за приключване time.Sleep(1 * time.Second) // Даваме време на горутините да приключат fmt.Println("Основната програма е приключила.") } -
Използване на
sync.WaitGroup(за изчакване, не за сигнализиране):WaitGroupсе използва за изчакване на завършването на група горутини, но сам по себе си не предоставя механизъм за спиране. Често се използва в съчетание с контекст или канали.package main import ( "context" "fmt" "sync" "time" ) func worker(ctx context.Context, wg *sync.WaitGroup, id int) { defer wg.Done() // Намалява брояча на WaitGroup при завършване на горутината for { select { case <-ctx.Done(): fmt.Printf("Горутината %d приключва\n", id) return default: fmt.Printf("Горутината %d работи...\n", id) time.Sleep(1 * time.Second) } } } func main() { ctx, cancel := context.WithCancel(context.Background()) var wg sync.WaitGroup numWorkers := 2 wg.Add(numWorkers) // Инициализираме WaitGroup for i := 1; i <= numWorkers; i++ { go worker(ctx, &wg, i) } time.Sleep(3 * time.Second) cancel() // Сигнал за анулиране wg.Wait() // Изчакваме всички горутини да приключат fmt.Println("Основната програма е приключила.") }
Изборът на метод зависи от конкретния случай и сложността на взаимодействията между горутините. Обикновено за каскадни отменяния се предпочита контекст.