在過去的幾年中,go語言的發展是驚人的,並且吸引了很多由其他語言(python、php、ruby)轉向go語言的跨語言學習者。 go語言太容易實現併發了,以至於它在很多地方被不正確的使用了。
在過去的幾年中,go語言的發展是驚人的,並且吸引了很多由其他語言(python、php、ruby)轉向go語言的跨語言學習者。
在過去的很長時間裡,很多開發人員和初創公司都習慣使用python、php或ruby快速開發功能強大的系統,並且大多數情況下都不需要擔心內部事務如何工作,也不需要擔心執行緒安全性和併發性。直到最近幾年,多執行緒高併發的系統開始流行起來,我們現在不僅需要快速開發功能強大的系統,而且還要保證被開發的系統能夠足夠快速執行。(我們真是太難了☺️)
對於被go語言天生支援併發的特性吸引來的跨語言學習者來說,我覺著掌握go語言的語法並不是最難的,最難的是突破既有的思維定勢,真正理解併發和使用併發來解決實際問題。
go語言太容易實現併發了,以至於它在很多地方被不正確的使用了。
有一些錯誤是很常見的,比如不考慮併發安全的單例模式。就像下面的示例**:
package singletontype singleton struct {}
var instance *singleton
func getinstance() *singleton // 不是併發安全的
} return instance
}
在上述情況下,多個goroutine可以執行第乙個檢查,並且它們都將建立該singleton
型別的例項並相互覆蓋。無法保證它將在此處返回哪個例項,並且對該例項的其他進一步操作可能與開發人員的期望不一致。
不好的原因是,如果有**保留了對該單例例項的引用,則可能存在具有不同狀態的該型別的多個例項,從而產生潛在的不同**行為。這也成為除錯過程中的乙個噩夢,並且很難發現該錯誤,因為在除錯時,由於執行時暫停而沒有出現任何錯誤,這使非併發安全執行的可能性降到了最低,並且很容易隱藏開發人員的問題。
也有很多對這種併發安全問題的糟糕解決方案。使用下面的**確實能解決併發安全問題,但會帶來其他潛在的嚴重問題,通過加鎖把對該函式的併發呼叫變成了序列。
var mu sync.mutexfunc getinstance() *singleton
}return instance
}
在上面的**中,我們可以看到在建立單例例項之前通過引入sync.mutex
和獲取lock來解決併發安全問題。問題是我們在這裡執行了過多的鎖定,即使我們不需要這樣做,在例項已經建立的情況下,我們應該簡單地返回快取的單例例項。在高度併發的**基礎上,這可能會產生瓶頸,因為一次只有乙個goroutine可以獲得單例例項。
因此,這不是最佳方法。我們必須考慮其他解決方案。
在c ++和其他語言中,確保最小程度的鎖定並且仍然是併發安全的最佳和最安全的方法是在獲取鎖定時利用眾所周知的check-lock-check
模式。該模式的偽**表示如下。
if check()}}
該模式背後的思想是,你應該首先進行檢查,以最小化任何主動鎖定,因為if語句的開銷要比加鎖小。其次,我們希望等待並獲取互斥鎖,這樣在同一時刻在那個塊中只有乙個執行。但是,在第一次檢查和獲取互斥鎖之間,可能有其他goroutine獲取了鎖,因此,我們需要在鎖的內部再次進行檢查,以避免用另乙個例項覆蓋了例項。
如果將這種模式應用於我們的getinstance()
方法,我們會寫出類似下面的**:
func getinstance() *singleton }}return instance
}
通過使用sync/atomic
這個包,我們可以原子化載入並設定乙個標誌,該標誌表明我們是否已初始化例項。
import "sync"import "sync/atomic"
var initialized uint32
... // 此處省略
func getinstance() *singleton
mu.lock()
defer mu.unlock()
if initialized == 0
atomic.storeuint32(&initialized, 1)
}return instance
}
但是……這看起來有點繁瑣了,我們其實可以通過研究go語言和標準庫如何實現goroutine同步來做得更好。
我們希望利用go慣用的方式來實現這個單例模式。我們在標準庫sync
中找到了once
型別。它能保證某個操作僅且只執行一次。下面是來自go標準庫的原始碼(部分注釋有刪改)。
// once is an object that will perform exactly one action.type once struct
func (o *once) do(f func())
}func (o *once) doslow(f func())
}
這說明我們可以借助這個實現只執行一次某個函式/方法,once.do()
的用法如下:
once.do(func() )
下面就是單例實現的完整**,該實現利用sync.once
型別去同步對getinstance()
的訪問,並確保我們的型別僅被初始化一次。
package singletonimport (
"sync"
)type singleton struct {}
var instance *singleton
var once sync.once
func getinstance() *singleton
})return instance
}
因此,使用sync.once
包是安全地實現此目標的首選方式,類似於objective-c和swift(cocoa)實現dispatch_once
方法來執行類似的初始化。
當涉及到併發和並行**時,需要對**進行更仔細的檢查。始終讓你的團隊成員執行**審查,因為這樣的事情很容易就會被發現。
所有剛轉到go語言的新開發人員都必須真正了解併發安全性如何工作以更好地改進其**。即使go語言本身通過允許你在對併發性知識知之甚少的情況下設計併發**,也完成了許多繁重的工作。在某些情況下,單純的依靠語言特性也無能為力,你仍然需要在開發**時應用最佳實踐。
翻譯自考慮到可讀性部分內容有修改。
Go 單例模式
單例模式,是一種常用的軟體設計模式,在它的核心結構中只包含乙個被稱為單例的特殊類。通過單例模式可以包在系統中乙個類只有乙個例項且該例項易於訪問,從而方便對例項個數的控制並節約系統資源。懶漢模式 lazy loading 懶漢模式是開源專案中使用最多的一種,最大的缺點是非執行緒安全的。type sin...
golang mysql 單例 Go的單例模式
單例模式是一種常用的軟體設計模式。在它的核心結構中只包含乙個被稱為單例類的特殊類。通過單例模式可以保證系統中乙個類只有乙個例項而且該例項易於外界訪問,從而方便對例項個數的控制並節約系統資源。如果希望在系統中某個類的物件只能存在乙個,單例模式是最好的解決方案。1.go實現非執行緒安全的單例模式 懶漢 ...
學習Go語言之單例模式
單例模式涉及到乙個單一的類,該類負責建立自己的物件,同時確保只有單個物件被建立。這個類提供了一種訪問其唯一的物件的方式,可以直接訪問,不需要例項化該類的物件 單例模式 2package main34 import 5 fmt 6 sync 7 89 type manager struct 10 11...