你真的會用go語言寫單例模式嗎?


  最近在學習Golang,想着可以就以前的知識做一些串通,加上了解到go語言也是面向對象編程語言之后。在最近的開發過程中,我碰到一個問題,要用go語言實現單例模式。本着“天下知識,同根同源”(我瞎掰的~),我心想,這有什么難的,可是真正做起來,還是碰到了不少問題。

  下面是我的經歷:

  1.我先是完成了我的第一版單例模式,就是非並發,最簡單的一種,懶漢模式:

var instance *single
type single struct{
	Name string
}
func GetInstance()*single{
	if m == nil{
		m = &single{}
	}
	return m
}
func main(){
	a := GetInstance()
	a.Name = "a"
	b := GetInstance()
	b.Name = "b"
	fmt.Println(&a.Name, a)
	fmt.Println(&b.Name, b)
	fmt.Printf("%p %T\n", a, a)
	fmt.Printf("%p %T\n", b, b)
}

  結果如下:

0xc04203e1b0 &{b}
0xc04203e1b0 &{b}
0xc04203e1b0 *main.single
0xc04203e1b0 *main.single

  可以看到,我們已經實現了簡單的單例模式,我們申請了兩次實例,在改變一個第二個實例的字段之后,第一個也隨之改變了。而且從他們的地址都相同也可以看出是同一個對象。但是,這樣簡陋的單例模式在並發下就容易出錯,非線程安全的。

  現在我們是在並發的情況下去調用的 GetInstance函數,現在恰好第一個goroutine執行到m = &Manager {}這句話之前,第二個goroutine也來獲取實例了,第二個goroutine去判斷m是不是nil,因為m = &Manager{}還沒有來得及執行,所以m肯定是nil,現在出現的問題就是if中的語句可能會執行兩遍!

  2.緊接着我們做了一些改進,給單例模式加了鎖:

var m *single
var lock sync.Mutex
type single struct{
	Name string
}
func GetInstance()*single{
	lock.Lock()
	defer lock.Unlock()
	if m == nil{
		m = &single{}
	}
	return m
}

  結果同上。

  與此同時,新的問題出現了,在高並發環境下,現在不管什么情況下都會上一把鎖,而且加鎖的代價是很大的,有沒有辦法繼續對我們的代碼進行進一步的優化呢?

  3.雙重鎖機制:

var m *single
var lock sync.Mutex
type single struct{
	Name string
}
func GetInstance()*single{
	if m == nil{
		lock.Lock()
		defer lock.Unlock()
		if m == nil{
			m = &single{}
		}
	}
	return m
}

  這次我們用了兩個判斷,而且我們將同步鎖放在了條件判斷之后,這樣做就避免了每次調用都加鎖,提高了代碼的執行效率。理論上寫到這里已經是很完美的單例模式了,但是我們在go語言里,我們有一個很優雅的寫法。

  4.sync包里的Once.Do()方法

var m *single
var once sync.Once

type single struct{
	Name string
}
func GetInstance()*single{
	once.Do(func() {
		m = &single{}
	})
	return m
}

  Once.Do方法的參數是一個函數,這里我們給的是一個匿名函數,在這個函數中我們做的工作很簡單,就是去賦值m變量,而且go能保證這個函數中的代碼僅僅執行一次!

 

  以后在用go語言寫單例模式的時候,可不要再傻傻的去使用前面那些例子了,既然已經有了優雅又強大的方法,我們直接用就完了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM