Почему этот сервер курсов валют не компилируется и верна ли его дисциплина RWMutex?
Этот сервер обновляет курсы валют раз в минуту в фоновой goroutine и отдаёт их по HTTP. Он не компилируется. Также оцените, верна ли его дисциплина RWMutex.
func main() {
mu &sync.RWMutex{}
rates, _ := readConversionRates()
go func() {
for {
time.Sleep(time.Minute)
tmp, err := readConversionRates()
if err != nil {
continue
}
mu.Lock()
rates = tmp
mu.Unlock()
}
}()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
mu.RLock()
defer mu.RUnlock()
rate := rates["RUB"]
fmt.Fprint(w, 140.0/rate)
})
if err := http.ListenAndServe(":8080", nil); err != http.ErrServerClosed {
log.Fatal(err)
}
}
Найдите и исправьте ошибку.
В mu &sync.RWMutex{} пропущен := — нужно mu := &sync.RWMutex{}. В остальном дисциплина блокировок верна: updater переприсваивает rates под Lock, хендлеры читают под RLock, поэтому каждый доступ защищён. Проверку != http.ErrServerClosed на ListenAndServe оставьте.
- ✗Считать, что пропущенный
:=— опечатка вместо=:muне объявлена, компилируется только:= - ✗Думать, что переприсваивание map под Lock гонится с RLock-читателями — блокировка сериализует оба
- ✗Считать чистое завершение
ListenAndServeошибкой — оно возвращаетhttp.ErrServerClosed
- →Почему переприсваивание map
ratesподLockбезопасно, а мутация подRLock— нет? - →Что сломается, если фоновый updater подменял бы
ratesподRLockвместоLock?
Сервер с фоновым обновлением курсов
package main
import (
"fmt"
"log"
"net/http"
"sync"
"time"
)
func main() {
mu &sync.RWMutex{} // FIXME: не компилируется
rates, err := readConversionRates()
if err != nil {
log.Fatalf("read initial conversion rates: %s", err)
}
// фоновая задача: обновляет курсы раз в минуту
go func() {
for {
time.Sleep(time.Minute)
tmp, err := readConversionRates()
if err != nil {
log.Printf("ERR: update conversion rates: %s", err)
continue
}
mu.Lock()
rates = tmp // переприсваиваем map целиком под Lock
mu.Unlock()
}
}()
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
from, val := "RUB", 140.0
mu.RLock()
defer mu.RUnlock()
rate, ok := rates[from] // читаем под RLock
if !ok {
http.NotFound(w, r)
return
}
fmt.Fprint(w, val/rate)
})
if err := http.ListenAndServe(":8080", nil); err != http.ErrServerClosed {
log.Fatal(err)
}
}
func readConversionRates() (map[string]float64, error) {
time.Sleep(100 * time.Millisecond) // имитация долгого I/O
return map[string]float64{"USD": 1.0, "RUB": 70.0}, nil
}
Единственная ошибка компиляции — строка mu &sync.RWMutex{}: пропущен оператор короткого объявления :=. Правильно:
mu := &sync.RWMutex{}
Дисциплина блокировок при этом корректна. Фоновый updater переприсваивает переменную rates целиком под Lock, а хендлеры читают её под RLock. Поскольку и запись, и чтение проходят через один и тот же mu, гонки нет: Lock исключает все RLock, а happens-before, который даёт Unlock → RLock, делает новую map видимой читателям.
Важно понимать, почему здесь работает именно переприсваивание, а не мутация на месте: переменная rates защищена мьютексом, поэтому подмена указателя на новую map безопасна. Если бы updater вместо этого писал rates[k] = v под RLock, это была бы запись в map под блокировкой чтения — настоящая гонка данных.
Проверка if err := http.ListenAndServe(...); err != http.ErrServerClosed тоже верна: ListenAndServe всегда возвращает не-nil ошибку, а при штатном завершении через Shutdown — именно http.ErrServerClosed, которую не нужно считать фатальной.