传统的线程模型(例如,通常在编写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 许可协议。转载请注明出处!