Приведите пример ошибки синхронизации в многопоточном коде на Go при работе с мапой без мьютекса и опишите, как race detector и sync.Map или мьютекс помогут решить проблему
Пример ошибки и краткие способы исправления. Баг (несинхронизированный доступ к мапе): ```go package main import ( "fmt" "time" ) var m = make(map[int]int) func main() { for i := 0; i < 1000; i++ { go func(i int) { m[i] = i }(i) // запись go func(i int) { _ = m[i] }(i) // чтение } time.Sleep(time.Second) fmt.Println("done") } ``` При запуске этот код часто падает с `fatal error: concurrent map writes` или ведёт к data race. Почему это проблема: - Встроенная `map` в Go не потокобезопасна: одновременные запись и чтение/запись без синхронизации приводят к гонке и возможному краху. Как race detector помогает: - Запустите с флагом race: `go run -race` или `go test -race`. Ран-датектор инструментирует доступы к памяти и при обнаружении гонки выдаст отчёт с трассировкой стека, указывая места конфликтующих обращений — это помогает локализовать проблему. Исправление 1 — мьютекс (правильно для общего случая): ```go package main import ( "fmt" "sync" "time" ) var ( mu sync.RWMutex m = make(map[int]int) ) func main() { for i := 0; i < 1000; i++ { go func(i int) { mu.Lock() m[i] = i mu.Unlock() }(i) go func(i int) { mu.RLock() _ = m[i] mu.RUnlock() }(i) } time.Sleep(time.Second) fmt.Println("done") } ``` - `sync.Mutex` или `sync.RWMutex` обеспечивает атомарность и упорядоченность доступов; RWMutex позволяет конкурентным чтениям идти параллельно. Исправление 2 — sync.Map (готовое потокобезопасное хранилище): ```go package main import ( "fmt" "sync" "time" ) var m sync.Map func main() { for i := 0; i < 1000; i++ { go func(i int) { m.Store(i, i) }(i) go func(i int) { m.Load(i) }(i) } time.Sleep(time.Second) fmt.Println("done") } ``` - `sync.Map` безопасна для конкурентного доступа без внешней блокировки; подходит для кейсов с частыми чтениями и редкими модификациями, но имеет другие характеристики производительности и API (ключ/значение — `interface{}`). Краткие рекомендации: - Для простых случаев используйте `sync.Mutex`/`sync.RWMutex`. - Для специализированных сценариев с высокой долей чтений рассмотрите `sync.Map`. - Всегда проверяйте подозрительные участки кода с `-race` перед релизом.
Баг (несинхронизированный доступ к мапе):
```go
package main
import (
"fmt"
"time"
)
var m = make(map[int]int)
func main() {
for i := 0; i < 1000; i++ {
go func(i int) { m[i] = i }(i) // запись
go func(i int) { _ = m[i] }(i) // чтение
}
time.Sleep(time.Second)
fmt.Println("done")
}
```
При запуске этот код часто падает с `fatal error: concurrent map writes` или ведёт к data race.
Почему это проблема:
- Встроенная `map` в Go не потокобезопасна: одновременные запись и чтение/запись без синхронизации приводят к гонке и возможному краху.
Как race detector помогает:
- Запустите с флагом race: `go run -race` или `go test -race`. Ран-датектор инструментирует доступы к памяти и при обнаружении гонки выдаст отчёт с трассировкой стека, указывая места конфликтующих обращений — это помогает локализовать проблему.
Исправление 1 — мьютекс (правильно для общего случая):
```go
package main
import (
"fmt"
"sync"
"time"
)
var (
mu sync.RWMutex
m = make(map[int]int)
)
func main() {
for i := 0; i < 1000; i++ {
go func(i int) {
mu.Lock()
m[i] = i
mu.Unlock()
}(i)
go func(i int) {
mu.RLock()
_ = m[i]
mu.RUnlock()
}(i)
}
time.Sleep(time.Second)
fmt.Println("done")
}
```
- `sync.Mutex` или `sync.RWMutex` обеспечивает атомарность и упорядоченность доступов; RWMutex позволяет конкурентным чтениям идти параллельно.
Исправление 2 — sync.Map (готовое потокобезопасное хранилище):
```go
package main
import (
"fmt"
"sync"
"time"
)
var m sync.Map
func main() {
for i := 0; i < 1000; i++ {
go func(i int) { m.Store(i, i) }(i)
go func(i int) { m.Load(i) }(i)
}
time.Sleep(time.Second)
fmt.Println("done")
}
```
- `sync.Map` безопасна для конкурентного доступа без внешней блокировки; подходит для кейсов с частыми чтениями и редкими модификациями, но имеет другие характеристики производительности и API (ключ/значение — `interface{}`).
Краткие рекомендации:
- Для простых случаев используйте `sync.Mutex`/`sync.RWMutex`.
- Для специализированных сценариев с высокой долей чтений рассмотрите `sync.Map`.
- Всегда проверяйте подозрительные участки кода с `-race` перед релизом.