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

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

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

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

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

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

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

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

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

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

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

Проверяется тестами (2)
  • Public go test ./...
  • health
    GET /health200
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)

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