goroutines如何运作?(或:goroutines和OS线程关系)

omr*_*umi 55 concurrency go goroutine

在调用系统调用时,其他goroutine如何继续执行?(当使用GOMAXPROCS = 1时)
据我所知,在调用系统调用时,线程会放弃控制,直到系统调用返回为止.Go如何在不使用block-on-syscall goroutine创建系统线程的情况下实现这种并发性?

文档:

够程

它们被称为goroutines,因为现有的术语 - 线程,协同程序,进程等 - 传达了不准确的内涵.goroutine有一个简单的模型:它是一个与同一地址空间中的其他goroutine同时执行的函数.它是轻量级的,比堆栈空间的分配花费更多.并且堆栈开始很小,因此它们很便宜,并且通过根据需要分配(和释放)堆存储来增长.

Goroutines被多路复用到多个OS线程上,因此如果应该阻塞,例如在等待I/O时,其他线程继续运行.他们的设计隐藏了线程创建和管理的许多复杂性.

One*_*One 37

如果goroutine正在阻塞,则运行时将启动一个新的OS线程来处理其他goroutine,直到阻塞的一个停止阻塞.

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

  • 它实际上将不同的 goroutine 复用到线程中(每个线程 1+ 个 goroutine)。如果一个阻塞,另一个将切换到在同一线程上运行。这个操作只需要改变3个寄存器,这就是为什么它这么轻。 (3认同)

omr*_*umi 27

好的,所以这就是我所学到的:当你进行原始系统调用时,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
        }
}
Run Code Online (Sandbox Code Playgroud)

在运行它时,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)
}
Run Code Online (Sandbox Code Playgroud)

因此,它是IOLoop和每个阻塞系统调用的线程之间的混合.

  • Go运行时有一个叫做网络轮询器的东西https://golang.org/src/runtime/netpoll.go它使用目标操作系统上可用的非阻塞本机API来处理网络IO,这样你就可以使用网络IO完成所有goroutine的操作. . (2认同)

nos*_*nos 13

它不能.当GOMAXPROCS = 1时,只有一个goroutine可以运行,无论是一个goroutine正在进行系统调用还是其他什么.

但是,从Go执行的系统调用时,大多数阻塞系统调用(例如套接字I/O)等待计时器都不会被阻止.它们由Go运行时复用到OS为多路复用I/O提供的epoll,kqueue或类似工具.

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

  • 因为`GOMAXPROCS`不能处理可以运行的goroutine的数量.至少,不是在问题的背景下.并且`GOMAXPROCS`不会阻止创建更多线程.它只是阻止它在多个处理器上执行它们...... (4认同)
  • @Elwinar `GOMAXPROCS` 设置同时运行 Go 代码的系统线程数。由于每个 go 代码都与一个 goroutine 相关联,因此这有效地限制了同时运行的 go 例程的数量。另外,nos 很好地解释了即使“GOMAXPROCS”为 1,也可以有“多个”系统线程。此外,处理器与此无关。 (2认同)

Elw*_*nar 5

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

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


Mis*_*aOS 5

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