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

Горутины и sync.WaitGroup: конкурентный запуск и ожидание

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

0 / 1 баллов

Параллельный подсчёт голосов

Когда голосование завершается и бюллетени поступают на подсчёт, организаторы нередко делят их на независимые пачки — по участкам, районам или просто удобным порциям — чтобы несколько счётчиков работали одновременно и не мешали друг другу. Именно такая параллельная модель позволяет обработать большой массив данных быстрее, чем если бы один человек последовательно просматривал каждый бюллетень.

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

Задача состоит в том, чтобы реализовать функцию, которая принимает набор пачек голосов и возвращает точную суммарную цифру — ту самую, которую организаторы огласят после завершения параллельного подсчёта. Корректность результата должна сохраняться как для нескольких непустых пачек, так и в вырожденном случае, когда голоса не поступили вовсе.

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

Реализуйте CountVotes. Перед каждой горутиной вызывайте wg.Add(1), внутри гарантируйте Done через defer. Сначала вычисляйте локальную сумму пачки, затем защищайте sync.Mutex только обновление общего итога. Возвращайте результат после wg.Wait().

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

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

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

Сначала выполняется go test ./..., затем сервис собирается и проверяется HTTP-запросами. Внешняя сеть и скачивание модулей отключены.

vote_test.go
package main

import (
    "context"
    "os"
    "os/exec"
    "path/filepath"
    "testing"
    "time"
)

func TestCountVotes(t *testing.T) {
    cases := []struct{name string; batches [][]int; want int}{ {"several", [][]int{{3, 4}, {10}, {1, 2, 3}}, 23}, {"empty", nil, 0}, {"negative correction", [][]int{{8, -1}, {2}}, 9} }
    for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { if got := CountVotes(tc.batches); got != tc.want { t.Errorf("got=%d want=%d", got, tc.want) } }) }
}
func TestCountVotesHasNoDataRace(t *testing.T) {
    source, err := os.ReadFile("vote.go"); if err != nil { t.Fatal(err) }; dir := t.TempDir()
    if err := os.WriteFile(filepath.Join(dir, "vote.go"), source, 0o600); err != nil { t.Fatal(err) }; if err := os.WriteFile(filepath.Join(dir, "go.mod"), []byte("module race.local/votes\n\ngo 1.27\n"), 0o600); err != nil { t.Fatal(err) }
    probe := `package main
import ("runtime"; "testing")
func TestRaceProbe(t *testing.T) { runtime.GOMAXPROCS(4); batches := make([][]int, 128); for i := range batches { batches[i] = make([]int, 128); for j := range batches[i] { batches[i][j] = 1 } }; for run := 0; run < 32; run++ { if got := CountVotes(batches); got != 16384 { t.Fatalf("got=%d", got) } } }
`
    if err := os.WriteFile(filepath.Join(dir, "race_probe_test.go"), []byte(probe), 0o600); err != nil { t.Fatal(err) }
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second); defer cancel(); command := exec.CommandContext(ctx, "go", "test", "-race", "-count=1", "."); command.Dir = dir; command.Env = append(os.Environ(), "GOWORK=off")
    output, err := command.CombinedOutput(); if ctx.Err() != nil { t.Fatal("race probe timed out") }; if err != nil { t.Fatalf("race-free implementation required:\n%s", output) }
}
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)

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

Начать заново?

Помощник начнёт новый диалог по этой задаче. Текущая переписка останется в истории до конца срока хранения.

О переписке

Можно приложить до трёх фото PNG, JPEG или WebP по 5 МБ.

Переписка и фото хранятся 90 дней после последнего принятого сообщения. Преподаватель получит доступ к диалогу, когда вы попросите его помочь.

Проверка решения остаётся отдельным действием.