Go 部落格
透過通訊共享記憶體
傳統的執行緒模型(例如,在編寫 Java、C++ 和 Python 程式時常用的模型)要求程式設計師使用共享記憶體進行執行緒間的通訊。通常,共享資料結構由鎖保護,執行緒會爭奪這些鎖來訪問資料。在某些情況下,使用執行緒安全的資料結構(如 Python 的 Queue)可以使這種情況更容易處理。
Go 的併發原語——goroutines 和 channels——提供了一種優雅且獨特的方式來構建併發軟體。(這些概念有著 有趣的歷程,起源於 C. A. R. Hoare 的 Communicating Sequential Processes。)Go 鼓勵使用 channels 來在 goroutines 之間傳遞資料的引用,而不是顯式地使用鎖來協調對共享資料的訪問。這種方法確保在給定時間只有一個 goroutine 可以訪問資料。這個概念在文件 Effective Go(任何 Go 程式設計師都必讀的文件)中得到了總結。
不要透過共享記憶體來通訊;相反,透過通訊來共享記憶體。
考慮一個輪詢 URL 列表的程式。在傳統的執行緒環境中,您可能會這樣組織資料:
type Resource struct {
url string
polling bool
lastPolled int64
}
type Resources struct {
data []*Resource
lock *sync.Mutex
}
然後,一個 Poller 函式(其中許多將在單獨的執行緒中執行)可能看起來像這樣:
func Poller(res *Resources) {
for {
// get the least recently-polled Resource
// and mark it as being polled
res.lock.Lock()
var r *Resource
for _, v := range res.data {
if v.polling {
continue
}
if r == nil || v.lastPolled < r.lastPolled {
r = v
}
}
if r != nil {
r.polling = true
}
res.lock.Unlock()
if r == nil {
continue
}
// poll the URL
// update the Resource's polling and lastPolled
res.lock.Lock()
r.polling = false
r.lastPolled = time.Nanoseconds()
res.lock.Unlock()
}
}
這個函式大約有一頁長,並且需要更多細節才能使其完整。它甚至沒有包含 URL 輪詢邏輯(它本身只需要幾行),也不會優雅地處理耗盡 Resource 池的情況。
讓我們看看使用 Go 慣用語實現的相同功能。在此示例中,Poller 是一個函式,它從輸入 channel 接收要輪詢的 Resources,並在完成後將它們傳送到輸出 channel。
type Resource string
func Poller(in, out chan *Resource) {
for r := range in {
// poll the URL
// send the processed Resource to out
out <- r
}
}
前一個示例中的精細邏輯明顯缺失,我們的 Resource 資料結構不再包含記賬資料。事實上,剩下的都是重要的部分。這應該能讓您初步瞭解這些簡單語言特性的強大功能。
上面的程式碼片段省略了許多內容。有關使用這些想法的完整、慣用的 Go 程式的演練,請參閱 Codewalk 透過通訊共享記憶體。
下一篇文章: Defer、Panic 和 Recover
上一篇文章: Go 的宣告語法
部落格索引