在调用系统调用时,其他goroutine如何继续执行? (当使用GOMAXPROCS = 1时)
据我所知,在调用系统调用时,线程会放弃控制,直到系统调用返回为止。
Go如何在不使用block-on-syscall goroutine创建系统线程的情况下实现这种并发性?
够程
他们称之为goroutines,因为现有的术语 - 线程, 协同程序,进程等 - 传达不准确的内涵。一个 goroutine有一个简单的模型:它是一个并发执行的函数 与其他goroutine在同一地址空间。它很轻巧, 比堆栈空间的分配花费更多。和堆栈 从小开始,所以它们便宜,并通过分配(和释放)来增长 堆存储根据需要。
Goroutines被多路复用到多个操作系统线程上,如果有的话 阻止,例如在等待I / O时,其他人继续运行。其 设计隐藏了许多线程创建的复杂性 管理。
答案 0 :(得分:34)
如果goroutine正在阻塞,则运行时将启动一个新的OS线程来处理其他goroutine,直到阻塞的一个停止阻塞。
参考:https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ
答案 1 :(得分:26)
好的,这就是我所学到的: 当您进行原始系统调用时,Go确实会为每个阻塞goroutine创建一个线程。例如,请考虑以下代码:
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
}
}
运行它时,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和每个阻塞系统调用的线程之间的混合。
答案 2 :(得分:13)
它不能。当GOMAXPROCS = 1时,只有一个goroutine可以运行,无论是一个goroutine正在进行系统调用还是其他事情。
但是,从Go执行的系统调用时,大多数阻塞系统调用(例如套接字I / O)等待计时器都不会被阻止。它们由Go运行时复用到OS提供的用于多路复用I / O的epoll,kqueue或类似工具。
对于无法与epoll等多路复用的其他类型的阻塞系统调用,Go会生成一个新的OS线程,无论GOMAXPROCS设置如何(虽然这是Go 1.1中的状态,我不确定是否情况发生了变化)
答案 3 :(得分:5)
您必须区分处理器编号和线程编号:您可以拥有比物理处理器更多的线程,因此多线程进程仍然可以在单个核心处理器上执行。
正如你引用的文档所解释的那样,goroutine不是一个线程:它只是一个在一个专用于一大堆堆栈空间的线程中执行的函数。如果您的进程有多个线程,则此函数可由任一线程执行。因此,可以在其线程中放入因某种原因而阻塞的goroutine(系统调用,I / O,同步),而其他例程可以由另一个执行。
答案 4 :(得分:1)
我写了一篇文章,解释了它们如何与示例和图表一起使用。请随时在这里查看:https://osmh.dev/posts/goroutines-under-the-hood
答案 5 :(得分: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)
}
答案 6 :(得分:0)
来自 runtime 的文档:
<块引用>GOMAXPROCS 变量限制了可以同时执行用户级 Go 代码的操作系统线程的数量。代表Go代码在系统调用中可以阻塞的线程数没有限制;这些不计入 GOMAXPROCS 限制。
所以当一个操作系统线程被系统调用阻塞时,另一个线程可以启动 - 并且 GOMAXPROCS = 1
仍然满足。这两个线程没有同时运行。