programowanieProgramista Backend

Wyjaśnij, czym jest gorutyna w Go. Jak to działa od kuchni i czym różni się od wątków w innych językach?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Gorutyna to lekki wątek wykonawczy, zarządzany przez środowisko uruchomieniowe Go. Aby uruchomić nową gorutynę, używa się słowa kluczowego go przed wywołaniem funkcji. Pod spodem tworzona jest struktura opisująca stos i stan zadania, która zostaje dodana do kolejki planera gorutyn.

W przeciwieństwie do wątków OS, gorutyna ma znacznie mniejszy początkowy stos (zazwyczaj 2 KB), a stos automatycznie się rozszerza w razie potrzeby. Planer Go samodzielnie przydziela je do dostępnych wątków systemu operacyjnego (model M:N).

Główne różnice w porównaniu do wątków (threads):

  • Tworzą się szybciej i wymagają mniej pamięci
  • Planowane są przez środowisko uruchomieniowe Go, a nie przez OS
  • Automatycznie skalują się według rdzeni
  • Synchronizacja realizowana jest przez kanały, co zapobiega wielu klasom wyścigów

Przykład użycia gorutyn i kanałów:

package main import ( "fmt" "time" ) func worker(id int, jobs <-chan int, results chan<- int) { for j := range jobs { fmt.Printf("worker %d rozpoczął zadanie %d ", id, j) time.Sleep(time.Second) fmt.Printf("worker %d zakończył zadanie %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 } }

Pytanie podchwytliwe

Czy gorutyny Go mogą być wykonywane równolegle na kilku rdzeniach?

Często spotyka się błędną odpowiedź: "Nie, ponieważ Go używa zielonych wątków". W rzeczywistości, za pomocą zmiennej środowiskowej lub wywołania runtime.GOMAXPROCS(n), Go może równolegle wykonywać gorutyny na wszystkich dostępnych rdzeniach procesora.

Przykład:

import "runtime" func main() { runtime.GOMAXPROCS(4) // Pozwala na użycie 4 rdzeni ... }

Przykłady rzeczywistych błędów spowodowanych brakiem znajomości szczegółów tematu


Historia

W projekcie backendowym usługi na Go zrealizowano pulę pracowników przez gorutyny, ale programiści zapomnieli ograniczyć liczbę jednocześnie działających gorutyn. W rezultacie, przy zwiększonym obciążeniu, aplikacja uruchamiała tysiące gorutyn, co doprowadziło do wyczerpania pamięci i awarii usługi. Problem rozwiązano wprowadzając limit aktywnych gorutyn (np. przy pomocy semafora lub puli pracowników).


Historia

Jeden z pracowników błędnie zsynchronizował dane między gorutynami, używając zwykłych zmiennych globalnych bez mutexów lub kanałów. Spowodowało to wyścig danych (race condition), przez co okresowo pojawiały się błędy przy przetwarzaniu płatności. Problem został wykryty dopiero po uruchomieniu w produkcji.


Historia

W serwisie analizy danych pominięto moment przekazania nil-kanałów w select: po zamknięciu kanału select nadal czekał na dane, przez co część gorutyn „zawiesiła się”. Naprawiono poprzez przypisanie nil do zamkniętego kanału oraz właściwe przetwarzanie select.