Go 部落格

透過溝通來共用記憶體

Andrew Gerrand
2010 年 7 月 13 日

傳統執行緒模型 (例如,在撰寫 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 的宣告語法
部落格索引