SafeCounter не является безопасным: ++ состоит из чтения и записи, а map одновременно меняют многие горутины. Нужны mutex и явное ожидание завершения.
Гонки данных в небезопасном счётчике
SafeCounter не является безопасным: ++ состоит из чтения и записи, а map одновременно меняют многие горутины. Нужны mutex и явное ожидание завершения.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
В примере реализован простой счётчик, который предполагается использовать в многопоточной среде, однако доступ к общей структуре происходит без какой‑либо синхронизации. Нужно понять, какие ошибки возникают при одновременных вызовах методов и как правильно защитить внутреннее состояние от race‑condition.
type SafeCounter struct {
values map[string]int
}
func (ctr *SafeCounter) Inc(key string) {
ctr.values[key]++
}
func (ctr *SafeCounter) Value(key string) int {
return ctr.values[key]
}
func main() {
counter := SafeCounter{values: make(map[string]int)}
for i := 0; i < 1000; i++ {
go counter.Inc("somekey")
}
time.Sleep(time.Second)
fmt.Println(counter.Value("somekey"))
}
Ответ
SafeCounter не является безопасным: ++ состоит из чтения и записи, а map одновременно меняют многие горутины. Нужны mutex и явное ожидание завершения.
Защита методов:
type SafeCounter struct {
mu sync.RWMutex
values map[string]int
}
func (c *SafeCounter) Inc(key string) {
c.mu.Lock()
defer c.mu.Unlock()
if c.values == nil { c.values = make(map[string]int) }
c.values[key]++
}
func (c *SafeCounter) Value(key string) int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.values[key]
}
Подключите sync. В main перед каждым go делайте wg.Add(1), внутри — defer wg.Done(), после цикла — wg.Wait() вместо Sleep. Тогда при тысяче успешных Inc значение будет 1000. Чтение тоже обязано брать блокировку; нельзя копировать счётчик с использованным mutex.