Проверяет понимание конкурентного доступа к map в Go и отсутствия встроенной защиты от одновременной записи и чтения.
В Go встроенный тип map не предназначен для одновременного использования из нескольких горутин без синхронизации. Если одна горутина выполняет итерацию по map (чтение), а другая параллельно добавляет или удаляет элементы (запись), возникает состояние гонки (data race).
При обнаружении одновременного чтения и записи Go runtime может выбросить панику с сообщением вида: fatal error: concurrent map read and map write. Однако это не гарантируется — в некоторых случаях программа может работать некорректно без явной ошибки, что ещё опаснее, так как приводит к неопределённому поведению (чтение мусорных данных, повреждение структуры map).
package main
import (
"fmt"
"sync"
)
func main() {
m := make(map[int]int)
var wg sync.WaitGroup
wg.Add(2)
// Горутина-писатель
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
m[i] = i
}
}()
// Горутина-читатель
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
_ = m[i]
}
}()
wg.Wait()
fmt.Println("done")
}Этот код почти наверняка упадёт с паникой из-за конкурентного доступа.
Используйте механизмы синхронизации:
package main
import (
"fmt"
"sync"
)
func main() {
var mu sync.RWMutex
m := make(map[int]int)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
mu.Lock()
m[i] = i
mu.Unlock()
}
}()
go func() {
defer wg.Done()
for i := 0; i < 1000; i++ {
mu.RLock()
_ = m[i]
mu.RUnlock()
}
}()
wg.Wait()
fmt.Println("done safely")
}Также можно использовать sync.Map — специальную потокобезопасную версию map, оптимизированную для некоторых сценариев (например, когда ключи пишутся один раз, а читаются много раз).
Никогда не читайте и не пишите в обычную map из разных горутин без синхронизации. Используйте sync.Mutex, sync.RWMutex или sync.Map для безопасного конкурентного доступа. Это критически важно для стабильности и предсказуемости многопоточных Go-программ.