Goroutines 是如何工作的? (或者: goroutines 和 OS 线程的关系)

其他 goroutine 如何在调用系统调用时继续执行? (当使用 GOMAXPROCS = 1时)
据我所知,在调用系统调用时,线程放弃控制权,直到系统调用返回。 Go 如何在不创建每个阻塞的系统调用 goroutine 的系统线程的情况下实现这种并发性?

来自 文件:

Goroutines

它们之所以被称为 goroutines 是因为现有的术语ー线程, 协同程序、过程等等ーー传达不准确的内涵 Goroutine 有一个简单的模型: 它是一个并发执行的函数 在同一个地址空间中与其他 goroutine 连接。它是轻量级的, 成本只比堆栈空间的分配多一点点 从小处着手,因此成本较低,通过分配(和释放)实现增长 根据需要进行堆存储。

Goroutines 是多路复用到多个 OS 线程上的,所以如果需要的话 阻塞,例如在等待 I/O 时,其他的继续运行 设计隐藏了许多复杂的线程创建和 管理层。

24335 次浏览

不可能。当 GOMAXPROCS = 1时,一次只能运行一个 goroutine,不管这个 goroutine 是在执行系统调用还是其他什么。

但是,当从 Go 执行系统调用时,等待计时器的大多数阻塞系统调用(如套接字 I/O)不会被阻塞。它们被 Go 运行时多路复用到 epoll、 kqueue 或操作系统为多路复用 I/O 提供的类似设施上。

对于其他类型的阻塞系统调用,不能与诸如 epoll 之类的东西复用,Go 确实会产生一个新的 OS 线程,不管 GOMAXPROCS 设置如何(尽管这是 Go 1.1中的状态,我不确定情况是否改变了)

如果一个 goroutine 被阻塞,运行时将启动一个新的操作系统线程来处理其他 goroutine,直到阻塞的 goroutine 停止阻塞。

参考资料: https://groups.google.com/forum/#!topic/golang-nuts/2IdA34yR8gQ

您必须区分处理器数量和线程数量: 您可以比物理处理器拥有更多的线程,因此多线程进程仍然可以在单个核心处理器上执行。

正如您引用的文档解释的那样,goroutine 不是线程: 它只是在一个线程中执行的一个函数,该线程专用于一个堆栈空间块。如果您的进程有多个线程,那么这个函数可以由任何一个线程执行。因此,由于某种原因(系统调用、 I/O、同步)而阻塞的 goroutine 可以在其线程中执行,而其他例程可以由其他线程执行。

好吧,这就是我学到的: 在执行原始系统调用时,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 和每个阻塞系统调用一个线程之间的混合。

希望这个总和数的例子对你有所帮助。

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)
}

我写过一篇文章,解释了它们是如何使用示例和图表的。请随时在这里看看它: https://osmh.dev/posts/goroutines-under-the-hood

来自 运行时间的文档:

GOMAXPROCS 变量限制了可以同时执行用户级 Go 代码的操作系统线程的数量。代表 Go 代码在系统调用中可以阻塞的线程数没有限制; 这些线程不计入 GOMAXPROCS 限制。

因此,当一个 OS 线程被阻塞进行系统调用时,可以启动另一个线程——而 GOMAXPROCS = 1仍然满足。这两个线程没有同时运行。