Go 部落格
透過溝通來共用記憶體
傳統執行緒模型 (例如,在撰寫 Java、C++ 和 Python 程式時常用到) 要求程式設計人員使用共用記憶體在執行緒之間溝通。一般來說,共用資料結構會受到鎖的保護,而執行緒會爭搶那些鎖來存取資料。在某些情況下,可以用 Python 的 Queue 等執行緒安全資料結構讓情況變得更簡單。
Go 並行處理原語 (goroutine 和通道) 提供結構化並行軟體的優雅且不同的方法。(這些概念有 一個有趣的歷史,源自 C. A. R. Hoare 的 Communicating Sequential Processes。)Go 並非明確地使用鎖來協調對共用資料的存取,而是鼓勵使用通道來在 goroutine 之間傳遞資料的參考。此方法可確保在任何特定時間點,只有一個 goroutine 能存取資料。此概念在文件 Effective Go (所有 Go 程式設計人員的必讀文件) 中總結
別透過共用記憶體來溝通;而要透過溝通來共用記憶體。
考慮一下一個輪詢網站網址清單的程式。在傳統的執行緒環境中,可以這樣來建構其資料
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 輪詢邏輯(它本身只會是幾行),也不會優雅地處理耗盡資源清單的情況。
讓我們看看使用 Go 慣用語實作相同功能。在此範例中,Poller 是一個函式,從輸入通道接收要輪詢的資源,並在完成後將它們傳送給輸出通道。
type Resource string
func Poller(in, out chan *Resource) {
for r := range in {
// poll the URL
// send the processed Resource to out
out <- r
}
}
先前範例中的精妙邏輯明顯不見了,我們的資源資料結構也不再包含會計資料。事實上,剩下來的只有重要的部分。這應該會讓你對這些簡單語言功能的力量有所了解。
以上程式碼片段有許多遺漏。若要了解使用這些概念的完整 Go 慣用語程式,請參閱 Codewalk 透過通訊分享記憶體。
下一篇文章:Defer、Panic 和 Recover
上一篇文章:Go 的宣告語法
部落格索引