Race-детектор в Go на простом примере
Golang в своём арсенале имеет такую вещь, как race detector.
Посмотрим на следующий код:
type Cache struct { data map[string]int } func NewCache() *Cache { return &Cache{ data: make(map[string]int), } } func (c *Cache) Set(k string, v int) { c.data[k] = v } func (c *Cache) Get(k string) int { if v, ok := c.data[k]; ok { return v } return 0 }
Это возможная реализация простейшего кэша, у которого ключ — это строка, а значение — целое число.
Всё будет хорошо до тех пор, пока кэш используется неконкурентно. Как только речь заходит о чём-то асинхронном (нужно заметить, что язык go отлично для этого подходит), — тут могут появиться неожиданные сюрпризы.
Взглянем на использование нашего кэша:
c := NewCache() var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() for i := 0; i < 1000; i++ { c.Set("nick", 25) } }() wg.Add(1) go func() { defer wg.Done() for i := 0; i < 1000; i++ { c.Set("nick", 30) } }() wg.Wait() fmt.Println(c.Get("nick"))
Код вполне валидный с точки зрения языка, он компилируется, 2 корутины обновляют значение по ключу
Что выведет программа?
Вариантов несколько:
1) иногда мы можем увидеть 25;
2) иногда мы можем увидеть 30;
3) иногда наше приложение может упасть с ошибкой
Для того, чтобы найти проблему в нашем примере, достаточно воспользоваться ключем
Тогда при запуске мы увидим предупреждение следующего вида:
================== WARNING: DATA RACE Write at 0x00c00009c150 by goroutine 8: runtime.mapassign_faststr() /usr/lib/go-1.13/src/runtime/map_faststr.go:202 +0x0 main.main.func2() /home/yury/race.go:19 +0xb7 Previous write at 0x00c00009c150 by goroutine 7: runtime.mapassign_faststr() /usr/lib/go-1.13/src/runtime/map_faststr.go:202 +0x0 main.main.func1() /home/yury/race.go:19 +0xb7 ...
Советую периодически использовать флаг