Конкурентность: горутины, каналы и контекст

Каналы и select: передача данных и координация событий

Содержание курса

Конвейер показаний метеостанции

Метеостанция фиксирует температуру непрерывно — снимок за снимком — и передаёт показания дальше по мере их поступления. Где-то на другом конце этой цепочки стоит обработчик, задача которого собрать всё, что пришло, прежде чем источник иссякнет или поступит команда остановиться. Оба участника работают независимо, и именно это делает их взаимодействие нетривиальным: каждый должен вовремя узнать о решении другого, не оказавшись при этом в ситуации бесконечного ожидания.

Обработчик не может просто ждать только данных — он обязан одновременно следить и за сигналом остановки. Если источник передал все показания и закрыл канал, обработчик распознаёт это и аккуратно завершает сбор. Если же сигнал остановки приходит раньше, чем источник успевает исчерпать свои данные, обработчик прекращает приём, а источник узнаёт об этом через тот же сигнал и не пытается отправить что-либо в уже недоступный канал.

Всё это — классическая задача согласованного завершения двух горутин, где один общий сигнал служит координатором для обоих участников. Результатом работы программы должен быть срез температур, накопленных к моменту завершения: либо полный, если источник успел передать все значения, либо частичный, если остановка была вызвана досрочно.

Что нужно сделать

Реализуйте CollectTemperatures. Источник в отдельной горутине отправляет конечный values в канал и закрывает его. И источник, и сборщик должны выбирать между работой и <-stop через select. Сборщик распознаёт закрытие данных через value, ok и возвращает накопленный срез. Не используйте time.Sleep.

О данных в ответах

Используйте учебные данные. Не вставляйте пароли, токены, ключи доступа, паспортные и банковские данные, а также персональные данные других людей. Политика обработки данных.

Проверяется тестами (2)
  • Public go test ./...
  • health
    GET /health200
Go-проект · weather.go

Сначала выполняется 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)
    }
}
РешениеGo · Go 1.27 (Modal)

Отправьте решение, чтобы увидеть результаты тестов.