Первый фрагмент делает пять вызовов для ID 42 и содержит data race на dbHits++. Второй при корректно объявленных once и user выполнит загрузку один раз.
Практическая задача: разбор кода go
Первый фрагмент делает пять вызовов для ID 42 и содержит data race на dbHits++. Второй при корректно объявленных once и user выполнит загрузку один раз.
Короткий ответ
Что ответить на собеседовании
Подробный разбор
Ответ с пояснениями
Условие
type User struct {
ID int
Name string
}
var (
dbHits int
)
func getUserInfoFromDB(id int) User {
time.Sleep(100 * time.Millisecond)
dbHits++
return User{ID: id, Name: fmt.Sprintf("User-%d", id)}
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 5; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
fmt.Println(getUserInfoFromDB(i))
}(42)
}
wg.Wait()
fmt.Println("Обращений к базе данных:", dbHits)
}
func main() {
var wg sync.WaitGroup
j := 12345
for i := 0; i < 5000; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
once.Do(func() {
user = getUserInfoFromDB(42)
})
fmt.Println(user)
}(j)
}
wg.Wait()
fmt.Println("Обращений к базе данных:", dbHits)
}
Ответ
Первый фрагмент делает пять вызовов для ID 42 и содержит data race на dbHits++. Второй при корректно объявленных once и user выполнит загрузку один раз.
В первом примере порядок печати не определён, а точное значение счётчика нельзя гарантировать из-за несинхронизированного инкремента. WaitGroup ждёт завершения, но не защищает конкурентные записи. Во втором нужны общие var once sync.Once и var user User; возврат once.Do гарантирует завершение единственной инициализации и безопасное чтение результата. Это решение подходит только одному пользователю: для разных ID нужен keyed-кэш, а для подавления одновременно повторяющихся загрузок — подходящий механизм координации.