英文:
Why is Go considered partially preemptive?
问题
我正在尝试更好地理解Go语言中"preemptive"和"cooperative"的定义。维基百科对于"preemptive multitasking"(抢占式多任务处理)的解释如下:
在计算机中,preemption(抢占)是指临时中断正在执行的任务,并打算在稍后恢复执行。这个中断是由一个外部调度程序完成的,任务本身不提供任何协助或合作。
维基百科提到了"external scheduler"(外部调度程序)。我猜这里指的是调度器,更具体地说是分派程序,因为据我所知,调度器只负责选择下一个要执行的进程。
Go语言通常被称为部分抢占式,因为同步点/抢占点仅在函数调用时发生,而不是在任意给定的指令处。这是有道理的。但正如维基百科的定义所述,抢占是由外部调度程序完成的。
但是,难道不是每个进程或任务都是抢占式的吗?因为CPU可以在执行过程中停止执行任何进程,以切换到另一个进程。如果有任何澄清,我将不胜感激!
附加说明
我能想到的唯一解释是我们在谈论不同的抢占级别。一个是针对进程的,另一个是针对内核/用户线程的。在这种情况下,CPU调度程序选择下一个进程,而Go调度程序负责协程/线程。
英文:
I am trying to get a better understanding of the definition preemptive and cooperative in the context of Go. Wiki states for preemptive multitasking
> In computing, preemption is the act of temporarily interrupting an executing task, with the intention of resuming it at a later time. This interrupt is done by an external scheduler with no assistance or cooperation from the task.
Wiki states "external scheduler". I guess it means dispatcher to be more specific since afaik the scheduler only is responsible for choosing the next process in line.
Go is often referred to as partially preemptive since sync-points/preemption-points are only at function calls and not at any given instruction. That makes sense. But as the wiki definition states, preemptive is done by an external scheduler.
But isn't every process or task preemptive since the CPU can stop executing any process mid-execution in order to switch to another process? Any clarification is appreciated!
Addendum
The only explanation I could come up with is that we talk about different preemption levels. One for processes, and one for kernel/user-threads. In that case the CPU scheduler selects the next process but the Go scheduler is responsible for the goroutines/threads.
答案1
得分: 4
我的答案是基于Andrew和Daniel的答案,并完全基于这个讲座。
自从Go 1.14版本以来,Go调度器采用的是非协作式抢占式调度。每个Go协程在一定的时间片后被抢占。在Go 1.19.1版本中,时间片为10毫秒。
在我之前提到的讲座中,从20:35开始,你可以了解到调度器在1.0版本中如何使用纯协作式抢占,然后在Go 1.2中引入了编译器支持的抢占,最后演变成了当前的非协作式抢占。
英文:
My answer is built on top of Andrew and Daniel's answers, and entirely based on this talk.
Since go 1.14, the go scheduler is non-cooperative pre-emptive. Each go routine is pre-empted after a certain time slice. It's 10ms in go 1.19.1.
In the talk I mentioned earlier, starting 20:35, you can find the history about how the scheduler used purely co-operative pre-emption in 1.0, then compiler baked in pre-emption with go 1.2, and finally the current nature - non-coperative pre-emption.
答案2
得分: 3
你的附言是正确的。这里有两个不同的调度器在工作。一个是操作系统调度器,另一个是应用程序级别的调度器。你可以查看这篇文章:这篇文章。它正是你要找的内容:
正如我们在第一篇文章中讨论的那样,操作系统调度器是一个抢占式调度器。[...] 内核在做决策,一切都是非确定性的。
还有这段内容:
Go调度器是Go运行时的一部分,而Go运行时则嵌入在你的应用程序中。这意味着Go调度器在用户空间中运行,位于内核之上。当前的Go调度器实现不是一个抢占式调度器,而是一个合作式调度器。作为一个合作式调度器,调度器需要在代码中的安全点发生的明确定义的用户空间事件来做出调度决策。
总结一下,这里有两个不同的调度器。一个用于进程,一个用于Goroutines。
英文:
Your addendum is correct. There are different schedulers working here. One is the OS scheduler and one on the application level. Check out this article. It's exactly what you are looking for:
> As we discussed in the first post, the OS scheduler is a preemptive scheduler. [...] The kernel is making decisions and everything is non-deterministic.
And this:
> The Go scheduler is part of the Go runtime, and the Go runtime is built into your application. This means the Go scheduler runs in user space, above the kernel. The current implementation of the Go scheduler is not a preemptive scheduler but a cooperating scheduler. Being a cooperating scheduler means the scheduler needs well-defined user space events that happen at safe points in the code to make scheduling decisions.
Conlusion, there are two different schedulers. One for the process, one for Goroutines.
答案3
得分: 3
这里有几个要点。首先,维基百科的文章讨论的是操作系统级别的抢占,可能并不完全适用。Go协程调度不是由操作系统处理,而是由Go运行时处理。
实质上,进程/线程由操作系统/硬件处理,并且是抢占式的。Go运行时可以在同一个线程上运行不同的"Go协程"。(实际上,这是使Go独特的一点,它可以轻松地创建具有数百万个并发Go协程的应用程序。)
最初,Go协程是不可抢占的,但令人惊讶的是,这几乎没有引起问题,但是一个长时间运行的循环如果没有遇到抢占点,可能会独占一个线程。但多亏了Austin Clements的一些出色工作,几年前(Go 1.15?)解决了这个问题。所以我相信至少在更流行的架构上,Go协程是完全可抢占的,但你可能会遇到一些陈旧的评论声称相反。
希望这可以帮到你,但是为了回答你的具体问题:
但是难道不是每个进程或任务都是可抢占的吗...?
是的,但它们不是Go协程。
CPU调度器选择下一个进程,但是Go调度器负责协程/线程。
不,操作系统(使用CPU/MMU硬件设施)调度进程和线程。Go调度器只决定在它控制的特定线程上运行哪个Go协程,并且它从不控制超过GOMAXPROCS(非阻塞)个线程。
英文:
There are a few things here. First the Wikipedia article is talking about operating system level preemption which may not exactly apply. Go-routine scheduling is not handled by the OS but by the Go runtime.
In essence processes/threads are handled by the OS/hardware and are preemptive. The Go runtime can run different "go routines" on the same thread. (In fact this is one thing that makes Go unique - easily allowing you to create an application with millions of concurrent go-routines.)
Originally go-routines were not preemptive but surprisingly this caused few problems but a long-running loop that did not encounter preemption points could hog a thread. But thanks to some great work by Austin Clements this was addressed a couple of years ago (Go 1.15?). So I believe go-routines are fully preemptable at least on the more popular architectures, but you may encounter old comments that state otherwise.
I hope this helps but to answer your specific questions:
> But isn't every process or task preemptive...?
Yes, but they are not go-routines.
> the CPU scheduler selects the next process but
> the Go scheduler is responsible for the goroutines/threads.
No, the OS (using CPU/MMU hardware facilities) schedules processes and threads. The Go scheduler only decides which go-routine runs next on the specific thread(s) it controls, and it never controls more than GOMAXPROCS (non-blocked) threads.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论