Skip to content

once

sync.Once — это инструмент синхронизации в стандартной библиотеке Go, используемый для гарантии, что функция выполняется только один раз в конкурентной среде. Он обычно используется в таких сценариях, как ленивая инициализация, инициализация глобальных ресурсов и т.д., гарантируя, что определённая операция выполняется только один раз, даже когда несколько goroutine выполняются одновременно.

go
package main

import (
	"fmt"
	"sync"
)

func main() {
	var wg sync.WaitGroup
	var once sync.Once
	for i := range 10 {
		wg.Add(1)
		go func() {
			defer wg.Done()
			once.Do(func() {
				fmt.Println(i)
			})
		}()
	}
	wg.Wait()
}

В приведённом коде всего 10 goroutine, но независимо только одна goroutine выполнит fmt.Println(i) в once.Do(). Какая goroutine напечатает, зависит от того, какая goroutine первая достигнет once.Do().

Структура

go
type Once struct {
    done atomic.Uint32
    m    Mutex
}

Его внутренняя структура очень проста:

  1. done, атомарное значение, используется для указания, было ли выполнено
  2. m, блокировка мьютекса, используется для блокировки других goroutine, которые хотят выполнить

Его принцип заключается в блокировке перед выполнением, затем обновлении done и разблокировке после завершения выполнения.

Do

go
func (o *Once) Do(f func()) {
	if o.done.Load() == 0 {
		o.doSlow(f)
	}
}

func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done.Load() == 0 {
		defer o.done.Store(1)
		f()
	}
}

Код в целом очень прост, со следующим потоком:

  1. Напрямую загружает атомарное значение, если уже выполнено, немедленно возвращается
  2. Если не выполнено, пытается приобрести блокировку, здесь несколько goroutine могут конкурировать за мьютекс
  3. После того как победитель успешно приобрёл блокировку, он всё ещё должен проверить значение done снова, потому что после приобретения блокировки кто-то другой мог уже завершить последовательность приобретение-блокировки-выполнение-разблокировка, и вы только что были пробуждены.
  4. Выполняет целевую функцию
  5. Обновляет значение done
  6. Освобождает блокировку

Итоги

sync.Once — это очень лаконичный и эффективный инструмент синхронизации. Он гарантирует, что определённая операция выполняется только один раз в конкурентной среде, тем самым избегая дублирования работы или потери ресурсов.

Golang by www.golangdev.cn edit