传统的线程模型(例如,通常在编写Java,C++ 和 Python 程序时使用)要求程序员使用共享内存在线程之间进行通信。通常,共享数据结构受锁保护,线程争夺这些锁以访问数据。在某些情况下,通过使用线程安全的数据结构可以使操作变得更容易,例如 Python 的 Queue。
Go的并发原语 — goroutine 和 chan nel— 提供了一种优雅而独特的方式来构造并发软件( 这些概念有一个有趣的历史,始于 C. A. R. Hoare 的 Communicating Sequential Processes )。Go 并未显式地使用锁来达成对共享数据的访问,而是鼓励使用 channel 在goroutine 之间传递对数据的引用。该方法可确保在给定时间只有一个 goroutine 可以访问数据。该概念在Effective Go(Go程序员必读)中进行了总结:
Do not communicate by sharing memory; instead, share memory by communicating.
不要通过共享内存进行通信;而是通过通信共享内存。
考虑一个轮询 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 轮询逻辑(它本身只有几行),也不能优雅地应对资源池耗尽。
让我们看一下使用 Go 风格实现的相同功能。在此示例中,Poller 函数从输入 channel 接收要轮询的资源,并在完成后将其发送到输出 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 Share Memory By Communicating。
原文:https://blog.golang.org/share-memory-by-communicating
源代码:https://github.com/cyningsun/go-test
本文作者 : cyningsun
本文地址 : https://www.cyningsun.com/09-29-2019/share-memory-by-communicating-cn.html
版权声明 :本博客所有文章除特别声明外,均采用 CC BY-NC-ND 3.0 CN 许可协议。转载请注明出处!