Skip to content

once

sync.Once 是 Go 標准庫中的一個同步工具,用於確保某個函數在並發環境下只執行一次。它通常用於延遲初始化、全局資源的初始化等場景,確保某個特定的操作只會被執行一次,即使有多個協程並發執行。

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 個協程,但無論如何,只有一個協程會執行 once.Do() 中的 fmt.Println(i)。具體是哪一個協程會打印,取決於哪個協程最先到達 once.Do()

結構

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

它的內部結構非常簡單

  1. done,一個原子值,用於表示是否被執行過
  2. m,互斥鎖,用於阻塞其它想要執行的協程

它的原理就是在執行前先加鎖,然後更新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. 沒有執行過,則嘗試持有鎖,這裡可能是多個協程在競爭互斥鎖
  3. 當勝者成功持有鎖後,還需要再一次判斷done的值,因為在你持有鎖後,別人可能已經完成加鎖-執行-解鎖一條龍了,這時你才剛被喚醒。
  4. 執行目標函數
  5. 更新done
  6. 釋放鎖

小結

sync.Once 是一個非常簡潔而高效的同步工具,它確保了並發環境下某個操作只執行一次,從而避免了重復工作或資源的浪費。

Golang學習網由www.golangdev.cn整理維護