【问题标题】:Golang main difference from CSP-Language by HoareGolang 与 Hoare 的 CSP-Language 的主要区别
【发布时间】:2015-12-15 13:49:09
【问题描述】:

看看这句话取自The examples from Tony Hoare's seminal 1978 paper

Go 的设计深受 Hoare 论文的影响。尽管 Go 与论文中使用的示例语言有很大不同,但这些示例仍然很容易翻译。除了语法之外,最大的区别在于 Go 将并发通信的管道显式建模为通道,而 Hoare 语言的进程直接相互发送消息,类似于 Erlang。 Hoare 在第 7.3 节中暗示了这种可能性,但受限于“每个端口都连接到另一个进程中的另一个端口”,在这种情况下,这主要是句法上的差异。

我很困惑。

Hoare 语言中的进程直接相互通信。 Go 例程也可以直接相互通信,但使用通道。

那么 golang 的限制有什么影响。真正的区别是什么?

【问题讨论】:

    标签: go concurrency


    【解决方案1】:

    答案需要更全面地了解 Hoare 在 CSP 方面的工作。他的工作进展可以概括为三个阶段:

    • 基于 Dijkstra 的信号量,Hoare 开发了监视器。这些在 Java 中使用,除了 Java 的实现包含错误(参见 Welch 的文章 Wot No Chickens)。不幸的是,Java 忽略了 Hoare 的后期工作。

    • CSP 就是由此产生的。最初,CSP 需要从进程 A 直接交换到进程 B。Ada 和 Erlang 使用这种集合方式。

    • CSP 于 1985 年完成,当时 his Book 首次发布。这个 CSP 的最终版本包括 Go 中使用的通道。与 Hoare 在牛津的团队一起,David May 同时开发了 Occam,这是一种有意将 CSP 融入实用编程语言的语言。 CSP 和 Occam 相互影响(例如在The Laws of Occam Programming 中)。多年来,Occam 仅在 Transputer 处理器上可用,其架构针对 CSP 量身定制。最近,Occam has developed 以其他处理器为目标,还吸收了 Pi 演算以及其他通用同步原语。

    因此,为了回答最初的问题,将 Go 与 CSP 和 Occam 进行比较可能会有所帮助。

    1. Channels:CSP、Go 和 Occam 都具有相同的通道语义。此外,Go 可以轻松地将缓冲添加到通道中(Occam 没有)。

    2. Choices:CSP 定义了 internalexternal 选择。但是,Go 和 Occam 都只有一种选择:Go 中的 select 和 Occam 中的 ALT。事实证明,有两种 CSP 选择在实际语言中不太重要。

    3. Occam 的 ALT 允许条件保护,但 Go 的 select 不允许(有一个解决方法:通道别名可以设置为 nil 模仿相同的行为)。

    4. Mobility: Go 允许通过通道发送通道末端(连同其他数据)。这创建了一个动态变化的拓扑结构,超出了 CSP 中可能的范围,但是开发了 Milner 的 Pi 演算(从他的 CCS 中)来描述这种网络。

    5. 进程:goroutine 是一个分叉的进程;它在它想要的时候终止并且它没有父母。这不像 CSP / Occam,其中过程是组合的。

    一个例子在这里会有所帮助:首先是 Occam(n.b. 缩进很重要)

    SEQ
      PAR
        processA()
        processB()
      processC()
    

    其次是去

    go processA()
    go processB()
    processC()
    

    在 Occam 情况下,直到 processA 和 processB 都终止后 processC 才会启动。在 Go 中,processA 和 processB 非常快速地分叉,然后 processC 直接运行。

    1. 共享数据:CSP 并不真正直接关注数据。但有趣的是,Go 和 Occam 在共享数据方面存在重要区别。当多个 goroutine 共享一组公共数据变量时,可能会出现竞争条件; Go 出色的比赛检测器有助于消除问题。但 Occam 采取了不同的立场:在编译时阻止共享可变数据。

    2. 别名:与上述相关,Go 允许许多指针来引用每个数据项。 Occam 中不允许使用此类别名,因此减少了检测竞争条件所需的工作量。

    后两点不是关于 Hoare 的 CSP,而是更多关于 May 的 Occam。但它们是相关的,因为它们直接关系到安全的并发编码。

    【讨论】:

    • 不明白 Java 的错误 :)
    • 在 Hoare 的监视器中,可以使线程进入睡眠状态。当它醒来时,它处于相同状态;时间过去了,但没有什么能削弱它继续前进的能力。但是在 Java 中,线程将以不同的状态唤醒:它现在位于运行队列的后面。这是使用 while 而不是 if 来处理的,但 Welch 的示例显示了它如何导致病态行为,在他的示例中,导致一个或多个线程饥饿。
    • 我非常感兴趣的一件事是 Rust 的 CSP 版本。凭借 Rust 对内存所有权和可变性的了解,它有机会通过将所有权向下传递通道而不是数据来实现 CSP,并且仍然是 CSP。这可以使它非常快,并且具有一致的传输时间。我记得过去在 Transputers 上,芯片之间的通道比内部通道慢很多,这在对实时系统进行负载平衡时总是很麻烦……很高兴看到 CSP 在流行语言中回归!
    • 这是一个非常好的并行。在理论上,CSP 频道是关于事件的。通过通道交换数据可以有不同的实现,因此通过复制或使用引用、指针或所有权传递数据对我来说似乎同样有效。
    • 我写了pkg.go.dev/github.com/rickb777/process 来更好地在 Go 中建模 CSP 流程。它所做的只是在一组 goroutine 终止时处理连接
    【解决方案2】:

    这正是重点:在 Hoare 的初始论文(以及 Erlang)中使用的示例语言中,进程 A 直接与进程 B 对话,而在 Go 中,goroutine A 与通道 C 对话,goroutine B 监听通道 C。即在 Go 中,通道是显式的,而在 Hoare 的语言和 Erlang 中,它们是隐式

    请参阅this article 了解更多信息。

    【讨论】:

    • 这在历史上并不完整,并且在某种程度上歪曲了 Hoare 的工作。
    【解决方案3】:

    最近,我一直在非常密集地使用 Go 的通道,并且多年来一直在处理并发和并行性,尽管我无法自称知道这方面的一切。

    我认为您要问的是向频道发送消息和直接相互发送消息之间的细微差别是什么?如果我理解你,快速回答很简单。

    发送到通道为通道两侧的并行性/并发性提供了机会。美观且可扩展。

    我们生活在一个并发的世界中。从 A 到 B(异步)发送一长串连续的消息意味着 B 需要以与 A 发送消息几乎相同的速度处理消息,除非多个 B 实例有机会处理从通道,从而分担工作量。

    关于通道的好处是您可以拥有许多生产者/接收者 go-routines,它们能够将消息推送到队列,或从队列中消费并相应地处理它。

    如果你以线性方式思考,就像单核 CPU,并发基本上就像有一百万个工作要做。了解单核 CPU 一次只能做一件事,但也会看到它给人的错觉是很多事情同时发生。在执行某些代码时,操作系统需要等待一段时间才能从网络、磁盘、键盘、鼠标等返回,甚至某些进程会休眠一段时间,这给操作系统提供了执行其他操作的机会同时。这一切都发生得非常快,造成了并行的错觉。

    另一方面,并​​行性是不同的,因为作业可以在完全不同的 CPU 上运行,而不受其他 CPU 的影响,因此不会在与其他 CPU 相同的约束下运行(尽管大多数操作系统会在确保工作负载均匀分布以在所有 CPU 上运行方面做得很好——也许除了需要 CPU 的、不合作的 non-os-yielding-code 之外,但即便如此,操作系统也会驯服它们。

    关键是,拥有多核 CPU 意味着可以产生更多的并行性和并发性。

    想象一下,在一家银行排队等候多个可以帮助您的出纳员。如果任何柜员都没有为顾客服务,那么一位柜员会选择处理下一位顾客并变得忙碌,直到他们都变得忙碌为止。每当客户离开柜员时,该柜员就可以处理队列中的下一位客户。

    【讨论】:

      猜你喜欢
      • 2011-07-25
      • 2019-09-04
      • 2012-10-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-31
      • 2012-06-12
      相关资源
      最近更新 更多