首頁 > 軟體

GoLang中的sync包Once使用執行範例

2023-03-07 06:01:54

背景

在系統初始化的時候,某些程式碼只想被執行一次,這時應該怎麼做呢,沒有學習 Once 前,大家可能想到 宣告一個標識,表示是否初始化過,然後初始化這個標識加鎖,更新這個標識。

但是學會了 One 的使用可以更加簡單的解決這個問題

One簡介

Once 包主要用於在並行執行程式碼的時候,某部分程式碼只會被執行 一次。

Once 的使用也非常簡單,Once 只有一個 Do 方法,接收一個無引數無返回值的函數型別的引數 f,不管呼叫多少次 Do 方法,引數 f 只在第一次呼叫 Do 方法時執行。

範例

我們有一個Msg 引數,多個協程都會用到他,但是這個引數只用初始化一次就可以。

package main
import (
	"fmt"
	"sync"
	"time"
)
var msg string
func main() {
	var one sync.Once
	for i := 0; i < 5; i++ {
		go func(i int) {
			one.Do(func() { 
				fmt.Printf("%d 執行初始化!n", i)
				msg = "Your Need Data"
			})
			fmt.Println(msg)
		}(i)
	}
	time.Sleep(3* time.Second)
}

執行結果如下:

可以看到初始化的程式碼只被4號執行緒執行了一次, 其他協程都是直接讀的初始化的資料,並沒有執行初始化的函數。

注意

不要在 Do() 方法的引數方法中再次呼叫Do() 方法,因為執行這個Do() 方法的引數方法的時候,One 會持有一個鎖,如果再引數方法中再次呼叫Do() 方法,就會等待這個鎖釋放, 導致引數方法無法執行完畢,然後外層的Do 方法就一直無法釋放鎖,最後就成了死鎖。

錯誤範例:

package main
import (
	"fmt"
	"sync"
)
var msg string
var one sync.Once
func main() {
	one.Do(fun1)
}
func fun1(){
	fmt.Println("我是 fun1")
	one.Do(fun2)
}
func fun2(){
	fmt.Println("我是 fun2")
}

執行結果:

可以知道再 fun1() 中使用 Do() 方法呼叫 fun2 的時候形成了死鎖, 因為在 fun1() 執行過程中已將持有了該鎖,需要 fun1() 執行完畢才會釋放,然後因為使用 Do() 方法執行 fun2() 也會請求這個鎖, 會一直等待,導致 fun1() 不可能執行完, 也不可能釋放鎖。成了死鎖。

原始碼解讀

檢視原始碼

func (o *Once) Do(f func()) {
	if atomic.LoadUint32(&o.done) == 0 {
		// Outlined slow-path to allow inlining of the fast-path.
		o.doSlow(f)
	}
}
func (o *Once) doSlow(f func()) {
	o.m.Lock()
	defer o.m.Unlock()
	if o.done == 0 {
		defer atomic.StoreUint32(&o.done, 1)
		f()
	}
}

使用一個原子類作為標識,加鎖校驗和操作原子類,保證只會被一個協程執行。

Do 呼叫了 doSlow , 在 doSlow 中有defer 關鍵字,表示執行函數和釋放鎖是倒序執行,必須先執行完畢 if 判斷和裡面的 f() 才能釋放鎖。

到此這篇關於GoLang中的sync包Once使用執行範例的文章就介紹到這了,更多相關Go sync Once內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com