Общее решение "init-Once"
В общем / обычном случае самое простое решение инициализировать только один раз, только когда это действительно необходимо, - это использовать sync.Once
и метод Once.Do()
.
На самом деле вам не нужно возвращать какое-либо значение из функции, переданной в Once.Do()
, потому что вы можете сохранять значения, например, в. глобальные переменные в этой функции.
См. Этот простой пример:
var (
total int
calcTotalOnce sync.Once
)
func GetTotal() int {
// Init / calc total once:
calcTotalOnce.Do(func() {
fmt.Println("Fetching total...")
// Do some heavy work, make HTTP calls, whatever you want:
total++ // This will set total to 1 (once and for all)
})
// Here you can safely use total:
return total
}
func main() {
fmt.Println(GetTotal())
fmt.Println(GetTotal())
}
Вывод вышеизложенного (попробуйте на Go Playground ):
Fetching total...
1
1
Некоторые заметки:
- Вы можете добиться того же, используя мьютекс или
sync.Once
, но последний на самом деле быстрее, чем используя мьютекс.
- Если
GetTotal()
был вызван ранее, последующие вызовы GetTotal()
ничего не сделают, но вернут ранее вычисленное значение, это то, что Once.Do()
делает / обеспечивает. sync.Once
"отслеживает", если его метод Do()
был вызван ранее, и если это так, переданное значение функции больше не будет вызываться.
sync.Once
обеспечивает все потребности для того, чтобы это решение было безопасным для одновременного использования из нескольких программ, учитывая, что вы не изменяете или не обращаетесь к переменной total
напрямую из любого другого места.
Решение для вашего "необычного" случая
В общем случае предполагается, что total
доступен только через функцию GetTotal()
.
В вашем случае это не имеет места: вы хотите получить к нему доступ через GetTotal()
функцию и , которую вы хотите установить после вызова GetPage()
(если он еще не был установлен).
Мы можем решить это и с sync.Once
. Нам понадобится указанная выше функция GetTotal()
; и когда выполняется вызов GetPage()
, он может использовать тот же calcTotalOnce
, чтобы попытаться установить свое значение на полученной странице.
Это может выглядеть примерно так:
var (
total int
calcTotalOnce sync.Once
)
func GetTotal() int {
calcTotalOnce.Do(func() {
// total is not yet initialized: get page and store total number
page := getPageImpl()
total = page.Total
})
// Here you can safely use total:
return total
}
type Page struct {
Total int
}
func GetPage() *Page {
page := getPageImpl()
calcTotalOnce.Do(func() {
// total is not yet initialized, store the value we have:
total = page.Total
})
return page
}
func getPageImpl() *Page {
// Do HTTP call or whatever
page := &Page{}
// Set page.Total from the response body
return page
}
Как это работает? Мы создаем и используем один sync.Once
в переменной calcTotalOnce
. Это гарантирует, что его метод Do()
может вызывать функцию, переданную ему один раз , независимо от того, где и как вызывается этот метод Do()
.
Если кто-то сначала вызовет функцию GetTotal()
, то запустится литерал функции внутри нее, который вызовет getPageImpl()
для извлечения страницы и инициализации переменной total
из поля Page.Total
.
Если функция GetPage()
будет вызвана первой, это также вызовет calcTotalOnce.Do()
, который просто устанавливает значение Page.Total
в переменную total
.
Какой бы маршрут не был пройден первым, это изменит внутреннее состояние calcTotalOnce
, которое будет помнить, что вычисление total
уже выполнено, и дальнейшие вызовы calcTotalOnce.Do()
никогда не вызовут переданное ему значение функции.
Или просто использовать "нетерпеливую" инициализацию
Также обратите внимание, что если существует вероятность того, что это общее число должно быть получено в течение жизненного цикла вашей программы, оно может не стоить описанной выше сложности, поскольку вы можете так же легко инициализировать переменную один раз, когда она будет создана.
var Total = getPageImpl().Total
Или, если инициализация немного сложнее (например, требуется обработка ошибок), используйте пакетную функцию init()
:
var Total int
func init() {
page := getPageImpl()
// Other logic, e.g. error handling
Total = page.Total
}