英文:
How do goroutines work? (or: goroutines and OS threads relation)
问题
在使用GOMAXPROCS=1时,其他goroutine如何在调用系统调用时继续执行?
据我所知,当调用系统调用时,线程会放弃控制权,直到系统调用返回。
Go语言如何在不为每个阻塞在系统调用上的goroutine创建系统线程的情况下实现并发?
根据文档:
Goroutines(协程)
它们被称为goroutines,因为现有的术语(线程、协程、进程等)传达了不准确的内涵。goroutine具有简单的模型:它是在同一地址空间中与其他goroutine并发执行的函数。它是轻量级的,几乎只需要分配堆栈空间。堆栈的初始大小很小,所以它们很廉价,并且通过根据需要分配(和释放)堆存储来增长。
Goroutine被复用到多个操作系统线程上,因此如果一个goroutine阻塞,例如在等待I/O时,其他goroutine将继续运行。它们的设计隐藏了许多线程创建和管理的复杂性。
英文:
How can other goroutines keep executing whilst invoking a syscall? (when using GOMAXPROCS=1)
As far as I'm aware of, when invoking a syscall the thread gives up control until the syscall returns.
How can Go achieve this concurrency without creating a system thread per blocking-on-syscall goroutine?
From the documentation:
> Goroutines
>
> They're called goroutines because the existing terms—threads,
> coroutines, processes, and so on—convey inaccurate connotations. A
> goroutine has a simple model: it is a function executing concurrently
> with other goroutines in the same address space. It is lightweight,
> costing little more than the allocation of stack space. And the stacks
> start small, so they are cheap, and grow by allocating (and freeing)
> heap storage as required.
>
> Goroutines are multiplexed onto multiple OS threads so if one should
> block, such as while waiting for I/O, others continue to run. Their
> design hides many of the complexities of thread creation and
> management.
答案1
得分: 53
如果一个 goroutine 正在阻塞,运行时将会启动一个新的操作系统线程来处理其他的 goroutine,直到阻塞的 goroutine 停止阻塞。
参考链接:https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ
英文:
If a goroutine is blocking, the runtime will start a new OS thread to handle the other goroutines until the blocking one stops blocking.
Reference : https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ
答案2
得分: 35
好的,下面是我学到的内容:
当你使用原始系统调用时,Go确实为每个阻塞的goroutine创建一个线程。例如,考虑以下代码:
package main
import (
"fmt"
"syscall"
)
func block(c chan bool) {
fmt.Println("block() enter")
buf := make([]byte, 1024)
_, _ = syscall.Read(0, buf) // 在标准输入上进行无缓冲读取时阻塞
fmt.Println("block() exit")
c <- true // 告诉main()我们完成了
}
func main() {
c := make(chan bool)
for i := 0; i < 1000; i++ {
go block(c)
}
for i := 0; i < 1000; i++ {
_ = <-c
}
}
在运行该代码时,Ubuntu 12.04 报告该进程有1004个线程。
另一方面,当使用Go的HTTP服务器并向其打开1000个套接字时,只创建了4个操作系统线程:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
因此,这是一个IOLoop和每个阻塞系统调用一个线程的混合体。
英文:
Ok, so here's what I've learned:
When you're doing raw syscalls, Go indeed creates a thread per blocking goroutine. For example, consider the following code:
package main
import (
"fmt"
"syscall"
)
func block(c chan bool) {
fmt.Println("block() enter")
buf := make([]byte, 1024)
_, _ = syscall.Read(0, buf) // block on doing an unbuffered read on STDIN
fmt.Println("block() exit")
c <- true // main() we're done
}
func main() {
c := make(chan bool)
for i := 0; i < 1000; i++ {
go block(c)
}
for i := 0; i < 1000; i++ {
_ = <-c
}
}
When running it, Ubuntu 12.04 reported 1004 threads for that process.
On the other hand, when utilizing Go's HTTP server and opening 1000 sockets to it, only 4 operating system threads were created:
package main
import (
"fmt"
"net/http"
)
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hi there, I love %s!", r.URL.Path[1:])
}
func main() {
http.HandleFunc("/", handler)
http.ListenAndServe(":8080", nil)
}
So, it's a mix between an IOLoop and a thread per blocking system call.
答案3
得分: 23
它不能。当GOMAXPROCS=1时,只能同时运行一个goroutine,无论这个goroutine是在执行系统调用还是其他操作。
然而,大多数阻塞的系统调用(如套接字I/O、等待计时器)在Go中执行时并不会被阻塞在系统调用上。它们由Go运行时通过epoll、kqueue或类似的操作系统提供的多路复用I/O机制进行复用。
对于无法像epoll那样进行多路复用的其他类型的阻塞系统调用,Go会生成一个新的操作系统线程,而不管GOMAXPROCS的设置(尽管这是Go 1.1的状态,我不确定情况是否有所改变)。
英文:
It can't. There's only 1 goroutine that can be running at a time when GOMAXPROCS=1, whether that one goroutine is doing a system call or something else.
However, most blocking system calls, such as socket I/O, waiting for a timer are not blocked on a system call when performed from Go. They're multiplexed by the Go runtime onto epoll, kqueue or similar facilities the OS provides for multiplexing I/O.
For other kinds of blocking system calls that cannot be multiplexed with something like epoll, Go does spawn a new OS thread, regardless of the GOMAXPROCS setting (albeit that was the state in Go 1.1, I'm not sure if the situation is changed)
答案4
得分: 6
你必须区分处理器编号和线程编号:你可以拥有比物理处理器更多的线程,因此多线程进程仍然可以在单核处理器上执行。
正如你引用的文档所解释的那样,goroutine并不是一个线程:它只是在专用的一块堆栈空间中执行的函数。如果你的进程有多个线程,这个函数可以由任何一个线程执行。因此,一个由于某种原因(系统调用、I/O、同步)而阻塞的goroutine可以在它的线程中继续执行,而其他的goroutine可以由另一个线程执行。
英文:
You have to differentiate processor number and thread number: you can have more threads than physical processors, so a multi-thread process can still execute on a single core processor.
As the documentation you quoted explain, a goroutine isn't a thread: it's merely a function executed in a thread that is dedicated a chunk of stack space. If your process have more than one thread, this function can be executed by either thread. So a goroutine that is blocking for a reason or another (syscall, I/O, synchronization) can be let in its thread while other routines can be executed by another.
答案5
得分: 5
我已经写了一篇文章,用例子和图表解释了它们的工作原理。请随意在这里查看:https://osmh.dev/posts/goroutines-under-the-hood
英文:
I’ve written an article explaining how they work with examples and diagrams. Please feel free to take a look at it here: https://osmh.dev/posts/goroutines-under-the-hood
答案6
得分: 2
根据runtime文档的说明:
GOMAXPROCS变量限制了可以同时执行用户级Go代码的操作系统线程的数量。在代表Go代码进行系统调用时,被阻塞的线程数量没有限制;它们不计入GOMAXPROCS的限制。
因此,当一个操作系统线程因为系统调用而被阻塞时,可以启动另一个线程,并且仍然满足GOMAXPROCS = 1
。这两个线程不会同时运行。
英文:
From documentation of runtime:
> The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. There is no limit to the number of threads that can be blocked in system calls on behalf of Go code; those do not count against the GOMAXPROCS limit.
so when one OS thread is blocked for syscall, another thread can be started - and GOMAXPROCS = 1
is still satisfied. The two threads are NOT running simultaneously.
答案7
得分: 0
希望这个求和数字的例子对你有所帮助。
package main
import "fmt"
func put(number chan<- int, count int) {
i := 0
for ; i <= (5 * count); i++ {
number <- i
}
number <- -1
}
func subs(number chan<- int) {
i := 10
for ; i <= 19; i++ {
number <- i
}
}
func main() {
channel1 := make(chan int)
channel2 := make(chan int)
done := 0
sum := 0
//go subs(channel2)
go put(channel1, 1)
go put(channel1, 2)
go put(channel1, 3)
go put(channel1, 4)
go put(channel1, 5)
for done != 5 {
select {
case elem := <-channel1:
if elem < 0 {
done++
} else {
sum += elem
fmt.Println(sum)
}
case sub := <-channel2:
sum -= sub
fmt.Printf("atimta : %d\n", sub)
fmt.Println(sum)
}
}
close(channel1)
close(channel2)
}
希望对你有所帮助!
英文:
Hope this example of sum numbers helps you.
package main
import "fmt"
func put(number chan<- int, count int) {
i := 0
for ; i <= (5 * count); i++ {
number <- i
}
number <- -1
}
func subs(number chan<- int) {
i := 10
for ; i <= 19; i++ {
number <- i
}
}
func main() {
channel1 := make(chan int)
channel2 := make(chan int)
done := 0
sum := 0
//go subs(channel2)
go put(channel1, 1)
go put(channel1, 2)
go put(channel1, 3)
go put(channel1, 4)
go put(channel1, 5)
for done != 5 {
select {
case elem := <-channel1:
if elem < 0 {
done++
} else {
sum += elem
fmt.Println(sum)
}
case sub := <-channel2:
sum -= sub
fmt.Printf("atimta : %d\n", sub)
fmt.Println(sum)
}
}
close(channel1)
close(channel2)
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论