Реализуйте подавление дублирующихся вызовов. Если несколько горутин одновременно
просят один и тот же ключ, дорогая функция fn выполняется один раз, а её
результат (значение и ошибка) получают все ожидающие.
Зачем это нужно: популярный ключ в кеше протух, и сотня запросов одновременно идёт
в базу за одним и тем же значением (cache stampede). Через singleflight в базу
уйдёт один.
type Group struct {
// ...
}
// Do выполняет fn для key. Параллельные вызовы с тем же key
// дожидаются результата единственного выполнения fn.
func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error)
var g Group
var calls int32
load := func() (interface{}, error) {
atomic.AddInt32(&calls, 1)
time.Sleep(50 * time.Millisecond) // «поход в базу»
return "Анна", nil
}
var wg sync.WaitGroup
for i := 0; i < 20; i++ {
wg.Add(1)
go func() {
defer wg.Done()
v, err := g.Do("user:42", load)
fmt.Println(v, err) // у всех двадцати: Анна <nil>
}()
}
wg.Wait()
fmt.Println(atomic.LoadInt32(&calls)) // 1
Вызовы сливаются, только пока fn выполняется. Как только она вернулась, следующий
Do по этому ключу запустит fn заново:
g.Do("user:42", load) // после wg.Wait(): calls станет 2
Для разных ключей fn выполняется отдельно, они друг другу не мешают.
- 20 горутин вызывают
Do с одним ключом, пока fn заблокирована. Счётчик вызовов
fn равен 1, все двадцать получили одно и то же значение и одну и ту же ошибку.
- Два вызова с ключами
a и b дают два выполнения fn.
- Два последовательных
Do с одним ключом дают два выполнения fn.
- Нулевое значение
var g Group пригодно к работе без конструктора.
- Пока
fn по ключу slow заблокирована, Do по ключу fast возвращается сразу: мьютекс на время fn держать нельзя.
- Вызов, пришедший, когда
fn уже выполняется, присоединяется к ней (одно выполнение на двоих), а после завершения запись по ключу снята.
- Всё идёт под детектором гонок (
-race).
На что смотрит интервьюер: не держите мьютекс на время fn, иначе вызовы встанут
в очередь вместо того, чтобы слиться; не оставляйте запись в карте после завершения;
отдавайте результат ожидающим без гонки.