0 added
0 removed
Original
2026-01-01
Modified
2026-03-10
1
<p>Теги: go, golang, программирование на go, race-детектор, кэш</p>
1
<p>Теги: go, golang, программирование на go, race-детектор, кэш</p>
2
<p>Golang в своём арсенале имеет такую вещь, как<strong>race detector</strong>.</p>
2
<p>Golang в своём арсенале имеет такую вещь, как<strong>race detector</strong>.</p>
3
<p>Посмотрим на следующий код:</p>
3
<p>Посмотрим на следующий код:</p>
4
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 }<p>Это возможная реализация простейшего кэша, у которого ключ - это строка, а значение - целое число.</p>
4
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 }<p>Это возможная реализация простейшего кэша, у которого ключ - это строка, а значение - целое число.</p>
5
<p>Всё будет хорошо до тех пор, пока кэш используется неконкурентно. Как только речь заходит о чём-то асинхронном (нужно заметить, что язык go отлично для этого подходит), - тут могут появиться неожиданные сюрпризы.</p>
5
<p>Всё будет хорошо до тех пор, пока кэш используется неконкурентно. Как только речь заходит о чём-то асинхронном (нужно заметить, что язык go отлично для этого подходит), - тут могут появиться неожиданные сюрпризы.</p>
6
<p>Взглянем на использование нашего кэша:</p>
6
<p>Взглянем на использование нашего кэша:</p>
7
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"))<p>Код вполне валидный с точки зрения языка, он компилируется, 2 корутины обновляют значение по ключу nick.</p>
7
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"))<p>Код вполне валидный с точки зрения языка, он компилируется, 2 корутины обновляют значение по ключу nick.</p>
8
<h2>Что выведет программа?</h2>
8
<h2>Что выведет программа?</h2>
9
<p>Вариантов несколько: 1) иногда мы можем увидеть 25; 2) иногда мы можем увидеть 30; 3) иногда наше приложение может упасть с ошибкой fatal error: concurrent map writes.</p>
9
<p>Вариантов несколько: 1) иногда мы можем увидеть 25; 2) иногда мы можем увидеть 30; 3) иногда наше приложение может упасть с ошибкой fatal error: concurrent map writes.</p>
10
<p>Для того, чтобы найти проблему в нашем примере, достаточно воспользоваться ключем -race при сборке.</p>
10
<p>Для того, чтобы найти проблему в нашем примере, достаточно воспользоваться ключем -race при сборке.</p>
11
<p>Тогда при запуске мы увидим предупреждение следующего вида:</p>
11
<p>Тогда при запуске мы увидим предупреждение следующего вида:</p>
12
================== 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 ...<p>Советую периодически использовать флаг -race для выявления проблем с гонками. В продакшене же он существенно замедляет работу вашего приложения и не рекомендуется.</p>
12
================== 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 ...<p>Советую периодически использовать флаг -race для выявления проблем с гонками. В продакшене же он существенно замедляет работу вашего приложения и не рекомендуется.</p>
13
13