Historia pytania:
Od samego początku język Go udostępnił pakiet time, w którym znajdują się podstawowe funkcje do zarządzania czasem — time.Sleep i time.After. W przeciwieństwie do języków z systemowym snem (System.sleep), Go realizuje asynchroniczne timery przez swoje prymitywy, co jest ważne dla wielowątkowej pracy.
Problem:
Często programiści niewłaściwie używają time.Sleep do wprowadzania przerw między zadaniami, co jest niepożądane w programach współbieżnych. W paradygmacie Go właściwiej jest budować zarządzanie oczekiwaniem na zdarzenia za pomocą kanałów i time.After do integracji z select/kanałami.
Rozwiązanie:
time.Sleep(d) blokuje bieżącą gorutynę na d czasu, to prosty „sen”. time.After(d) zwraca kanał, na którym po d pojawi się zdarzenie-czas. Ostatnia opcja jest znacznie elastyczniejsza w select w kanałach, wygodna dla oczekiwań przerywanych, timeoutów.
Przykład kodu:
ch := make(chan struct{}) go func() { time.Sleep(2 * time.Second) ch <- struct{}{} }() select { case <-ch: fmt.Println("gotowe") case <-time.After(1 * time.Second): fmt.Println("timeout") }
Kluczowe cechy:
Czy można użyć time.Sleep do zablokowania wykonania całego programu?
Nie, time.Sleep „usypia” tylko jedną gorutynę, inne kontynuują pracę.
Czy time.After może prowadzić do wycieku pamięci, jeśli kanał nie jest odczytywany?
Tak, timer wisi dopóki wartość nie zostanie odczytana — jeśli nie ma odczytu, zbieracz śmieci nie usunie obiektu.
Przykład kodu:
func leak() { for { _ = time.After(time.Hour) } }
Jak poprawnie anulować oczekiwanie po time.After, jeśli nie otrzymano zdarzenia?
Lepiej użyć time.Timer i ręcznie zatrzymać, jeśli trzeba zakończyć oczekiwanie przed terminem:
t := time.NewTimer(time.Minute) if done { t.Stop() }
Gorutyna czeka na sygnał z pracy, a na wypadek timeoutu robi tak:
time.Sleep(10*time.Second) doSomething()
Zalety:
Wady:
Kod budowany jest przez select i time.After:
select { case <-workSignal: // Wykonać case <-time.After(10 * time.Second): // Zrobić po timeoutie }
Zalety:
Wady: