Каналы и select: передача данных и координация событий
Содержание курса
Конвейер показаний метеостанции
Метеостанция фиксирует температуру непрерывно — снимок за снимком — и передаёт показания дальше по мере их поступления. Где-то на другом конце этой цепочки стоит обработчик, задача которого собрать всё, что пришло, прежде чем источник иссякнет или поступит команда остановиться. Оба участника работают независимо, и именно это делает их взаимодействие нетривиальным: каждый должен вовремя узнать о решении другого, не оказавшись при этом в ситуации бесконечного ожидания.
Обработчик не может просто ждать только данных — он обязан одновременно следить и за сигналом остановки. Если источник передал все показания и закрыл канал, обработчик распознаёт это и аккуратно завершает сбор. Если же сигнал остановки приходит раньше, чем источник успевает исчерпать свои данные, обработчик прекращает приём, а источник узнаёт об этом через тот же сигнал и не пытается отправить что-либо в уже недоступный канал.
Всё это — классическая задача согласованного завершения двух горутин, где один общий сигнал служит координатором для обоих участников. Результатом работы программы должен быть срез температур, накопленных к моменту завершения: либо полный, если источник успел передать все значения, либо частичный, если остановка была вызвана досрочно.
Что нужно сделать
Реализуйте CollectTemperatures. Источник в отдельной горутине отправляет конечный values в канал и закрывает его. И источник, и сборщик должны выбирать между работой и <-stop через select. Сборщик распознаёт закрытие данных через value, ok и возвращает накопленный срез. Не используйте time.Sleep.
О данных в ответах
Используйте учебные данные. Не вставляйте пароли, токены, ключи доступа, паспортные и банковские данные, а также персональные данные других людей. Политика обработки данных.
- Public go test ./...
- healthGET /health → 200
Сначала выполняется go test ./..., затем сервис собирается и проверяется HTTP-запросами. Внешняя сеть и скачивание модулей отключены.
weather_test.go
package main
import (
"reflect"
"testing"
"time"
)
func waitForReadingsClose(t *testing.T, readings <-chan int) {
t.Helper(); select { case _, ok := <-readings: if ok { t.Fatal("producer sent after stop") }; case <-time.After(time.Second): t.Fatal("producer did not terminate after stop") }
}
func TestCollectTemperaturesNaturalCompletion(t *testing.T) {
stop := make(chan struct{}); want := []int{4, -2, 17, 17}; if got := CollectTemperatures(want, stop); !reflect.DeepEqual(got, want) { t.Fatalf("got=%v want=%v", got, want) }
}
func TestFeedStopsAndCloses(t *testing.T) {
stop := make(chan struct{}); readings := make(chan int); go feedTemperatures([]int{4, 9}, readings, stop)
if got := <-readings; got != 4 { t.Fatalf("first=%d", got) }; close(stop); waitForReadingsClose(t, readings)
}
func TestCollectorReturnsPartialResultOnStop(t *testing.T) {
stop := make(chan struct{}); readings := make(chan int); result := make(chan []int, 1); go func() { result <- collectTemperatures(readings, stop) }()
readings <- 4; close(stop); select { case got := <-result: if !reflect.DeepEqual(got, []int{4}) { t.Fatalf("got=%v", got) }; case <-time.After(time.Second): t.Fatal("collector did not stop") }
}
main.go
package main
import (
"io"
"log"
"net/http"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/plain; charset=utf-8")
w.WriteHeader(http.StatusOK)
_, _ = io.WriteString(w, "ok\n")
})
if err := http.ListenAndServe(":18080", mux); err != nil {
log.Fatal(err)
}
}
Отправьте решение, чтобы увидеть результаты тестов.
