一个Go goroutine是一个协程吗?

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

Is a Go goroutine a coroutine?

问题

在2012年的Google I/O大会上,Rob Pike在《Go并发模式》的演讲中提到,多个goroutine可以存在于一个线程中。这是否意味着它们是作为协程(coroutine)实现的?如果不是,它们是如何实现的?如果有的话,提供源代码的链接将会很有帮助。

英文:

In the Google I/O 2012 presentation Go Concurrency Patterns, Rob Pike mentions that several goroutines can live in one thread. Does this imply that they are implemented as coroutines? If not, how they are implemented? Links to source code would be welcome.

答案1

得分: 78

在我看来,协程意味着支持显式的方式来将控制权转移到另一个协程。也就是说,程序员以一种方式编写协程,当他们决定一个协程何时暂停执行并将控制权传递给另一个协程时(通过调用或返回/退出(通常称为yielding))。

Go的"goroutines"是另一回事:它们在某些不确定的点上隐式地放弃控制权<sup>1</sup>,这些点发生在goroutine即将在某个(外部)资源上休眠,比如I/O完成、通道发送等。这种方法结合了通过通道共享状态的能力,使程序员能够将程序逻辑编写为一组顺序的轻量级进程,从而消除了协程和事件驱动方法中常见的混乱代码问题。

关于实现,我认为它们与(不太为人所知的)"State Threads"库非常相似,只是更低级一些(因为Go不依赖于libc或类似的东西,直接与操作系统内核通信)——你可以阅读ST库的介绍性论文,其中对这个概念解释得很好。


<sup>1</sup> 实际上,这些点比协程的点更不确定,但比真正的操作系统线程在抢占式多任务处理下的点更确定,因为在抢占式多任务处理中,每个线程可能在任何给定的时间点和线程控制流中被内核挂起。
*2021-05-28更新:*实际上,自Go 1.14起,goroutines是(几乎)抢占式调度的
不过需要注意的是,它仍然不像典型内核对其管理的线程所做的那样强硬的抢占,但比以前更接近了;至少现在一个goroutine进入忙循环后不可能变为不可抢占。

英文:

IMO, a coroutine implies supporting of explicit means for transferring control to another coroutine. That is, the programmer programs a coroutine in a way when they decide when a coroutine should suspend execution and pass its control to another coroutine (either by calling it or by returning/exiting (usually called yielding)).

Go's "goroutines" are another thing: they implicitly surrender control at certain indeterminate points<sup>1</sup> which happen when the goroutine is about to sleep on some (external) resource like I/O completion, channel send etc. This approach combined with sharing state via channels enables the programmer to write the program logic as a set of sequential light-weight processes which removes the spaghetti code problem common to both coroutine- and event-based approaches.

Regarding the implementation, I think they're quite similar to the (unfortunately not too well-known) "State Threads" library, just quite lower-level (as Go doesn't rely on libc or things like this and talks directly to the OS kernel) &mdash; you could read the introductory paper for the ST library where the concept is quite well explained.


<sup>1</sup> In fact, these points are less determinate than those of coroutines but more determinate than with true OS threads under preemptive multitasking, where each thread might be suspended by the kernel at any given point in time and in the flow of the thread's control.
Update on 2021-05-28: actually, since Go 1.14, goroutines are scheduled (almost) preemptively.
It should be noted though, that it's still not that hard-core preemption a typical kernel does to the threads it manages but it's quite closer than before; at least it's now impossible for a goroutine to become non-preemptible once it enters a busy loop.

答案2

得分: 63

不完全正确。Go FAQ中的为什么使用goroutine而不是线程?部分解释了这个问题:

> Goroutine的目的是使并发易于使用。这个想法已经存在一段时间了,它是将独立执行的函数(协程)多路复用到一组线程上。当一个协程阻塞,比如通过调用阻塞系统调用,运行时会自动将同一操作系统线程上的其他协程移动到一个不会被阻塞的可运行线程上。程序员看不到这一切,这就是关键所在。我们称之为goroutine的结果可能非常廉价:除了用于栈的内存之外,它们几乎没有额外的开销,栈的大小只有几千字节。
>
> 为了使栈变小,Go的运行时使用可调整大小的有界栈。一个新创建的goroutine被分配了几千字节的内存,这几乎总是足够的。当不够时,运行时会自动增加(和缩小)用于存储栈的内存,允许许多goroutine在适量的内存中运行。CPU开销平均每个函数调用约为三个廉价指令。在同一地址空间中创建数十万个goroutine是可行的。如果goroutine只是线程,系统资源将在更小的数量上耗尽。

英文:

Not quite. The Go FAQ section Why goroutines instead of threads? explains:

> Goroutines are part of making concurrency easy to use. The idea, which has been around for a while, is to multiplex independently executing functions—coroutines—onto a set of threads. When a coroutine blocks, such as by calling a blocking system call, the run-time automatically moves other coroutines on the same operating system thread to a different, runnable thread so they won't be blocked. The programmer sees none of this, which is the point. The result, which we call goroutines, can be very cheap: they have little overhead beyond the memory for the stack, which is just a few kilobytes.
>
> To make the stacks small, Go's run-time uses resizable, bounded stacks. A newly minted goroutine is given a few kilobytes, which is almost always enough. When it isn't, the run-time grows (and shrinks) the memory for storing the stack automatically, allowing many goroutines to live in a modest amount of memory. The CPU overhead averages about three cheap instructions per function call. It is practical to create hundreds of thousands of goroutines in the same address space. If goroutines were just threads, system resources would run out at a much smaller number.

答案3

得分: 5

无论一个 goroutine 是一个真正的协程还是类似的东西,这个问题经常在 https://groups.google.com/forum/?fromgroups=#!forum/golang-nuts 上进行讨论。有些人可能会争论这些微妙之处,但对于大多数情况来说,goroutine 就是一个协程。

请查看 https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit 了解调度器的工作原理。

英文:

Whether a goroutine is a proper coroutine or just something similar is often discussed on https://groups.google.com/forum/?fromgroups=#!forum/golang-nuts. Some people can argue about such subtleties, but for most of it: goroutine is a coroutine.

Have a look at https://docs.google.com/document/d/1TTj4T2JO42uD5ID9e89oa0sLKhJYD0Y_kqxDv3I3XMw/edit
to understand how the scheduler works.

答案4

得分: 2

Goroutine是一个独立的执行"线程"。我认为它与协程并不真正可比。在第一次近似中,goroutine可以通过真正的操作系统线程来实现。据我所知,这是早期版本的gccgo的情况。另一个区别是goroutine可以被抢占。

当前的Go编译器将goroutine实现为非常轻量级的用户空间"线程"。与绿色线程(green threads)等相比,一个明显的特点是goroutine可以切换到不同的操作系统线程。

我认为你可以在这里找到一些相关的有趣信息:proc.c

英文:

Goroutine is a separate "thread" of execution. It is IMO not really comparable to a coroutine. In the first approximation, goroutines can be implemented by real OS threads. AFAIK, that was the case of early versions of gccgo. Another difference is that goroutines can get preempted.

Current Go compilers implement goroutines as very lightweight, user space "threads". One distinct feature wrt to eg. green threads is that goroutines can get switched to different OS threads.

I think you can find some related bits of interest here: proc.c

答案5

得分: 1

根据Russ Cox的《Go语言的协程》一文:

简而言之:

  • goroutine 创建一个新的并发、并行的控制流
  • coroutine 创建一个新的并发、非并行的控制流

由于关于goroutine的回答已经很多了,下面是一些关于coroutine的更多细节,来自《Go语言的协程》:

  • 协程提供了并发而非并行:当一个协程在运行时,被它恢复或让出的协程不会同时运行。协程是编写希望在程序结构上实现并发而非并行的程序的有用构建块。

  • 由于协程一次只运行一个,并且只在程序的特定点进行切换,协程可以在彼此之间共享数据而不会出现竞争条件。显式的切换作为同步点,创建happens-before边界。

  • 由于调度是显式的(没有任何抢占)并且完全在操作系统之外完成,协程切换大约需要十纳秒,通常甚至更短。启动和清理的成本也比线程低得多。

  • goroutine的切换时间大约为几百纳秒,因为Go运行时负责一部分调度工作。然而,goroutine仍然提供了线程的完全并行性和抢占性。

英文:

Per Coroutines for Go by Russ Cox

In Short

  • goroutine creates a new concurrent, parallel control flow
  • coroutine creates a new concurrent, non-parallel control flow.

Since there are more answers about the goroutine, here are some more details of coroutines from the Coroutines for Go

> - Coroutines provide concurrency without parallelism: when one coroutine is running, the one that resumed or yielded to it is not. Coroutines are a useful building block for writing programs that want concurrency for program structuring but not for parallelism

> - Because coroutines run one at a time and only switch at specific points in the program, the coroutines can share data among themselves without races. The explicit switches serve as synchronization points, creating happens-before edges.

> - Because scheduling is explicit (without any preemption) and done entirely without the operating system, a coroutine switch takes around ten nanoseconds, usually even less. Startup and teardown are also much cheaper than threads.

> - a goroutine switch is closer to a few hundred nanoseconds because the Go runtime takes on some of the scheduling work. However, goroutines still provide the full parallelism and preemption of threads.

答案6

得分: 0

维基百科上说:“协程没有一个确切的定义。”Goroutine 是否是协程取决于你选择的协程定义。如果你阅读维基百科上的协程页面,大部分描述实际上并不适用于 goroutine。例如,goroutine 不使用 yield/resume。

英文:

Wikipedia says, "There is no single precise definition of coroutine." Whether goroutines are coroutines depends on which definition of coroutine you choose. If you read the Wikipedia page on coroutines, most of what is described really doesn't match goroutines at all. For example, goroutines don't use yield/resume.

huangapple
  • 本文由 发表于 2013年8月5日 20:28:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/18058164.html
匿名

发表评论

匿名网友

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

确定