1. 程式人生 > >轉: Go -- 單例

轉: Go -- 單例

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

  下面是我的經歷:

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

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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.緊接著我們做了一些改進,給單例模式加了鎖:

1 2 3 4 5 6 7 8 9 10 11 12 13 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.雙重鎖機制:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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()方法

1 2 3 4 5 6 7 8 9 10 11 12 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能保證這個函式中的程式碼僅僅執行一次!