Golang与Hoare的CSP语言相比的主要区别是什么?

huangapple go评论78阅读模式
英文:

Golang main difference from CSP-Language by Hoare

问题

看一下这个摘自托尼·霍尔(Tony Hoare)1978年的经典论文的陈述:

Go的设计受到霍尔的论文的强烈影响。尽管Go与论文中使用的示例语言有很大的区别,但这些示例仍然相对容易转换。除了语法之外,最大的区别是Go将并发通信的通道明确地建模为通道,而霍尔的语言中的进程直接向彼此发送消息,类似于Erlang。霍尔在第7.3节中暗示了这种可能性,但有一个限制:“每个端口只能连接到另一个进程中的一个端口”,在这种情况下,这将是一个主要的语法差异。

我有点困惑。

霍尔的语言中的进程直接相互通信。Go协程也直接相互通信,但是使用通道。

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

英文:

Look at this statement taken from The examples from Tony Hoare's seminal 1978 paper:

> Go's design was strongly influenced by Hoare's paper. Although Go differs significantly from the example language used in the paper, the examples still translate rather easily. The biggest difference apart from syntax is that Go models the conduits of concurrent communication explicitly as channels, while the processes of Hoare's language send messages directly to each other, similar to Erlang. Hoare hints at this possibility in section 7.3, but with the limitation that "each port is connected to exactly one other port in another process", in which case it would be a mostly syntactic difference.

I'm confused.

Processes in Hoare's language communicate directly to each other. Go routines communicate also directly to each other but using channels.

So what impact has the limitation in golang. What is the real difference?

答案1

得分: 46

答案需要对Hoare在CSP方面的工作有更全面的理解。他的工作可以总结为三个阶段:

  • 基于Dijkstra的信号量,Hoare开发了监视器。这在Java中被使用,但Java的实现中存在一个错误(参见Welch的文章Wot No Chickens)。很遗憾,Java忽视了Hoare的后期工作。

  • CSP就是从这里发展起来的。最初,CSP要求从进程A直接交换到进程B。这种会合方法被Ada和Erlang使用。

  • CSP在1985年完成,当时他的书首次出版。CSP的最终版本包括了Go中使用的通道。与Hoare在牛津的团队一起,David May同时开发了Occam,这是一种有意将CSP融入实际编程语言的语言。CSP和Occam互相影响(例如在Occam编程法则中)。多年来,Occam只能在Transputer处理器上使用,该处理器的架构经过调整以适应CSP。最近,Occam已经发展以针对其他处理器,并吸收了Pi演算以及其他一般的同步原语。

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

  1. 通道:CSP、Go和Occam对于通道的语义是相同的。此外,Go使得在通道中添加缓冲变得容易(Occam不支持)。

  2. 选择:CSP定义了内部选择外部选择。然而,Go和Occam都只有一种选择:Go中的select和Occam中的ALT。实际语言中存在两种CSP选择的事实被证明不那么重要。

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

  4. 可移动性:Go允许通过通道发送通道端点(以及其他数据)。这创建了一个动态变化的拓扑结构,超出了CSP的可能性,但是Milner的Pi演算是为了描述这种网络而开发的(基于他的CCS)。

  5. 进程:goroutine是一个分叉的进程;它在希望终止时终止,并且没有父进程。这与CSP/Occam不太相似,其中进程是组合的。

这里有一个例子:首先是Occam(注意缩进)

SEQ
  PAR
    processA()
    processB()
  processC()

其次是Go

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。但它们是相关的,因为它们直接涉及到安全的并发编码。

英文:

The answer requires a fuller understanding of Hoare's work on CSP. The progression of his work can be summarised in three stages:

  • based on Dijkstra's semaphore's, Hoare developed monitors. These are as used in Java, except Java's implementation contains a mistake (see Welch's article Wot No Chickens). It's unfortunate that Java ignored Hoare's later work.

  • CSP grew out of this. Initially, CSP required direct exchange from process A to process B. This rendezvous approach is used by Ada and Erlang.

  • CSP was completed by 1985, when his Book was first published. This final version of CSP includes channels as used in Go. Along with Hoare's team at Oxford, David May concurrently developed Occam, a language deliberately intended to blend CSP into a practical programming language. CSP and Occam influenced each other (for example in The Laws of Occam Programming). For years, Occam was only available on the Transputer processor, which had its architecture tailored to suit CSP. More recently, Occam has developed to target other processors and has also absorbed Pi calculus, along with other general synchronisation primitives.

So, to answer the original question, it is probably helpful to compare Go with both CSP and Occam.

  1. Channels: CSP, Go and Occam all have the same semantics for channels. In addition, Go makes it easy to add buffering into channels (Occam does not).

  2. Choices: CSP defines both the internal and external choice. However, both Go and Occam have a single kind of selection: select in Go and ALT in Occam. The fact that there are two kinds of CSP choice proved to be less important in practical languages.

  3. Occam's ALT allows condition guards, but Go's select does not (there is a workaround: channel aliases can be set to nil to imitate the same behaviour).

  4. Mobility: Go allows channel ends to be sent (along with other data) via channels. This creates a dynamically-changing topology and goes beyond what is possible in CSP, but Milner's Pi calculus was developed (out of his CCS) to describe such networks.

  5. Processes: A goroutine is a forked process; it terminates when it wants to and it doesn't have a parent. This is less like CSP / Occam, in which processes are compositional.

An example will help here: firstly Occam (n.b. indentation matters)

SEQ
  PAR
    processA()
    processB()
  processC()

and secondly Go

go processA()
go processB()
processC()

In the Occam case, processC doesn't start until both processA and processB have terminated. In Go, processA and processB fork very quickly, then processC runs straightaway.

  1. Shared data: CSP is not really concerned with data directly. But it is interesting to note there is an important difference between Go and Occam concerning shared data. When multiple goroutines share a common set of data variables, race conditions are possible; Go's excellent race detector helps to eliminate problems. But Occam takes a different stance: shared mutable data is prevented at compilation time.

  2. Aliases: related to the above, Go allows many pointers to refer to each data item. Such aliases are disallowed in Occam, so reducing the effort needed to detect race conditions.

The latter two points are less about Hoare's CSP and more about May's Occam. But they are relevant because they directly concern safe concurrent coding.

答案2

得分: 7

这正是关键所在:在Hoare最初的论文中使用的示例语言(以及Erlang语言)中,进程A直接与进程B通信,而在Go语言中,goroutine A与通道C通信,而goroutine B则监听通道C。也就是说,在Go语言中,通道是显式的,而在Hoare的语言和Erlang中,它们是隐式的。

请参阅这篇文章获取更多信息。

英文:

That's exactly the point: in the example language used in Hoare's initial paper (and also in Erlang), process A talks directly to process B, while in Go, goroutine A talks to channel C and goroutine B listens to channel C. I.e. in Go the channels are explicit while in Hoare's language and Erlang, they are implicit.

See this article for more info.

答案3

得分: 0

最近,我一直在 intensively 使用 Go 的 channels,并且多年来一直在处理并发和并行性,尽管我不能自称对此了解一切。

我想你问的是发送消息到通道和直接发送给对方之间的微妙区别是什么?如果我理解正确,简单的答案是:

向通道发送消息可以在通道的两端实现并行性/并发性。这是美妙的,可扩展的。

我们生活在一个并发的世界中。从 A 异步地向 B 发送一长串连续的消息意味着 B 需要以与 A 发送消息的速度几乎相同的速度处理这些消息,除非有多个 B 的实例有机会处理从通道中取出的消息,从而共享工作负载。

通道的好处在于,你可以有多个生产者/接收者的 go 协程,它们能够将消息推送到队列中,或者从队列中消费并相应地处理它。

如果你像一个单核 CPU 一样线性地思考,并发基本上就像有一百万个任务要做一样。知道单核 CPU 一次只能做一件事情,但同时又看到它给人一种许多事情同时发生的错觉。当执行一些代码时,操作系统需要等待一段时间,等待网络、磁盘、键盘、鼠标等返回,或者甚至等待一些进程休眠一段时间,这给操作系统在此期间做其他事情的机会。这一切发生得非常快,创造了并行性的错觉。

另一方面,并行性是不同的,因为作业可以在完全不同的 CPU 上运行,独立于其他 CPU 的情况,因此不受其他 CPU 的约束(尽管大多数操作系统在确保工作负载均匀分布在所有 CPU 上运行方面做得相当好,可能除了那些对 CPU 需求高、不合作、不让步的代码,但即使是这样,操作系统也会驯服它们)。

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

想象一下银行的一个单一队列,分散到一些可以帮助你的出纳员那里。如果没有任何出纳员为任何客户提供服务,一个出纳员会选择处理下一个客户并忙碌起来,直到他们全部忙碌。每当一个客户离开出纳员,那个出纳员就能处理队列中的下一个客户。

英文:

Recently, I've been working quite intensively with Go's channels, and have been working with concurrency and parallelism for many years, although I could never profess to know everything about this.

I think what you're asking is what's the subtle difference between sending a message to a channel and sending directly to each other? If I understand you, the quick answer is simple.

Sending to a Channel give the opportunity for parallelism / concurrency on both sides of the channel. Beautiful, and scalable.

We live in a concurrent world. Sending a long continuous stream of messages from A to B (asynchronously) means that B will need to process the messages at pretty much the same pace as A sends them, unless more than one instance of B has the opportunity to process a message taken from the channel, hence sharing the workload.

The good thing about channels is that that you can have a number of producer/receiver go-routines which are able to push messages to the queue, or consume from the queue and process it accordingly.

If you think linearly, like a single-core CPU, concurrency is basically like having a million jobs to do. Knowing a single-core CPU can only do one thing at a time, and yet also see that it gives the illusion that lots of things are happening at the same time. When executing some code, the time the OS needs to wait a while for something to come back from the network, disk, keyboard, mouse, etc, or even some process which sleeps for a while, give the OS the opportunity to do something else in the meantime. This all happens extremely quickly, creating the illusion of parallelism.

Parallelism on the other hand is different in that the job can be run on a completely different CPU independent of what's going with other CPUs, and therefore doesn't run under the same constraints as the other CPU (although most OS's do a pretty good job at ensuring workloads are evenly distributed to run across all of it's CPUs - with perhaps the exception of CPU-hungry, uncooperative non-os-yielding-code, but even then the OS tames them.

The point is, having multi-core CPUs means more parallelism and more concurrency can occur.

Imagine a single queue at a bank which fans-out to a number of tellers who can help you. If no customers are being served by any teller, one teller elects to handle the next customer and becomes busy, until they all become busy. Whenever a customer walks away from a teller, that teller is able to handle the next customer in the queue.

huangapple
  • 本文由 发表于 2015年9月18日 20:01:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/32651557.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定