Programowanie w Go - #15 Context - co to jest i jak tego używać
Tym razem zapraszam na nagranie o używaniu i stosowaniu Context (po polsku: kontekst - jakby nie patrzeć) w Go. Ten osobliwy interfejs, który w bibliotece standardowej pojawił się w sumie całkiem niedawno, posiada kilka ważnych zastosowań, z czego spróbuję te najciekawsze omówić na filmie. Z tego też względu, zdecydowałem się na zupełnie osobny odcinek o tym zagadnieniu w moim kursie programowania w Go. Tematykę tego odcinka można potraktować jako uzupełnienie odcinka 13 o goroutines i wielowątkowości.
Go i Context
Wstęp
Z samej dokumentacji języka Go, konkretnie paczki context, dowiemy się, że ten package (ten, który zawiera ten interfejs) definiuje typ Contextu, który jakby “transportuje” sygnały związane z deadline zadanych akcji (twardym limitem czasowym), cancellation akcji (ich przerywaniem) i innego tego typu informacjami, którymi operuje pojedynczy request (np. żądanie http). Służy to “łączeniu” wielu endpointów API oraz procesów, w taki sposób, aby w pewnych sytuacjach można było reagować na pewne specyficzne przypadki, które mogą zajść w aplikacji (np. nagłe zerwanie połączenia tcp).
W każdym razie, można to sobie wyobrazić jako hipotetyczne urządzenie, które np. wystrzeliwuje jednocześnie dużą ilość piłek przyczepionych do niego gumkach/linkach, a które to linki/gumki mają różną długość i elastyczność. Tu dla uproszczenia, zakładamy, że urządzenie wystrzeliwuje każdą piłkę z identyczną siłą i prędkością. Jedna linka może mieć długość 1m, druga 2m, trzecia 10m, z kolei elastyczność może być taka, że linka o długości 1m może się naciągnąć aż do 5m, a linka o długości 10m od razu pęknie, bo takiego “zapasu” mieć nie będzie. To urządzenie musi jednak “obsłużyć” wszystkie ich długości, elastyczności i “wykrywać”, aby np: nie czekać za długo na piłkę, na lince o długości 10m, bo jeśli nie wróci w czasie ustalonym dla takiego scenariusza siły wyrzutu, długości i elastyczności 10m linki, to na pewno linka “pękła” i nie ma co “czekać” na powrót piłki po wystrzale. Ten mechanizm, który pozwala urządzeniu w “orientowaniu” się w takiej sytuacji, te linki i algorytmy, pod tym możemy rozumieć właśnie kontekst.
Kompletnie nie wiem czy ta karkołomność przełożenia tego na nasz język w moim wykonaniu jest poprawna. Na pewno jest mocno eksperymentalna. W każdym razie mój opis może zabijać na miejscu strachliwych przed zdaniami wielokrotnie złożonymi, ale sam Context może być również używany do synchronizowania goroutines, niekoniecznie jednak w każdej sytuacji będzie to “optymalne” zastosowanie.
Nagranie z wyjaśnieniem interfejsu Context
Kod do prostego przykładu zastosowania Context
1package main
2
3import (
4 "context"
5 "fmt"
6 "time"
7)
8
9// based on http://jgao.io/
10func main() {
11 fmt.Println("Program started...")
12 start := time.Now()
13
14 ch := make(chan struct{})
15
16 // Initialise top-level context
17 ctx := context.Background()
18
19 // Context to pass to function
20 // contextWithTimeout, cancelTimeout := context.WithTimeout(ctx, 3100*time.Millisecond)
21 // go computation(contextWithTimeout, 3, "Timeout", ch)
22
23 contextWithDeadline, cancelDeadline := context.WithDeadline(ctx, time.Now().Add(4*time.Second))
24 go computation(contextWithDeadline, 3, "Deadline", ch)
25
26 defer cancelDeadline()
27
28 select {
29 case val := <-ch:
30 fmt.Println("Got result: ", val)
31 // case <-contextWithTimeout.Done():
32 // fmt.Println("Computation cancelled by timeout.")
33 case <-contextWithDeadline.Done():
34 fmt.Println("Computation cancelled by deadline.")
35 // default:
36 // cancel()
37 }
38
39 elapsed := time.Since(start)
40 fmt.Printf("Program finished... It took %s \n", elapsed)
41}
42
43func computation(ctx context.Context, seconds int, name string, c chan struct{}) {
44 fmt.Printf("%s: computing started...\n", name)
45
46 time.Sleep(time.Duration(seconds) * time.Second)
47 c <- struct{}{}
48
49 fmt.Printf("%s: computing finished...\n", name)
50}
Kod do przykładu z serwerem http
Do testowania tego przykładu użyj wybranego klienta http, np. curl, httpie, postman, itp.
1package main
2
3import (
4 "context"
5 "io"
6 "log"
7 "net/http"
8)
9
10func main() {
11 ctx, cancel := context.WithCancel(context.Background())
12
13 http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
14 io.WriteString(w, "Hi\n")
15 })
16
17 http.HandleFunc("/quit", func(w http.ResponseWriter, r *http.Request) {
18 io.WriteString(w, "Bye\n")
19 cancel()
20 })
21
22 srv := &http.Server{Addr: ":8123"}
23
24 go func() {
25 if err := srv.ListenAndServe(); err != nil {
26 log.Printf("Httpserver: ListenAndServe() error: %s", err)
27 }
28 }()
29
30 <-ctx.Done()
31
32 if err := srv.Shutdown(ctx); err != nil && err != context.Canceled {
33 log.Println(err)
34 }
35
36 log.Println("done.")
37}