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

时间:2014-07-06 19:40:41

标签: concurrency go goroutine

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

来自documentation

  

够程

     

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

     

Goroutines被多路复用到多个操作系统线程上,如果有的话   阻止,例如在等待I / O时,其他人继续运行。其   设计隐藏了许多线程创建的复杂性   管理。

7 个答案:

答案 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 仍然满足。这两个线程没有同时运行。