简体   繁体   English

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

[英]How do goroutines work? (or: goroutines and OS threads relation)

How can other goroutines keep executing whilst invoking a syscall?其他 goroutine 如何在调用系统调用时继续执行? (when using GOMAXPROCS=1) (当使用 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? Go 如何在不为每个系统调用阻塞 goroutine 创建系统线程的情况下实现这种并发性?

From the documentation :文档

Goroutines协程

They're called goroutines because the existing terms—threads, coroutines, processes, and so on—convey inaccurate connotations.它们被称为 goroutines 是因为现有的术语——线程、协程、进程等——传达了不准确的内涵。 A goroutine has a simple model: it is a function executing concurrently with other goroutines in the same address space. goroutine 有一个简单的模型:它是一个与同一地址空间中的其他 goroutine 并发执行的函数。 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. Goroutines 被多路复用到多个 OS 线程上,所以如果一个应该阻塞,例如在等待 I/O 时,其他人继续运行。 Their design hides many of the complexities of thread creation and management.它们的设计隐藏了线程创建和管理的许多复杂性。

If a goroutine is blocking, the runtime will start a new OS thread to handle the other goroutines until the blocking one stops blocking.如果一个 goroutine 正在阻塞,运行时将启动一个新的 OS 线程来处理其他 goroutine,直到阻塞的一个停止阻塞。

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

Ok, so here's what I've learned: When you're doing raw syscalls, Go indeed creates a thread per blocking goroutine.好的,这就是我学到的:当你进行原始系统调用时,Go 确实为每个阻塞 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.运行它时,Ubuntu 12.04 报告该进程有 1004 个线程。

On the other hand, when utilizing Go's HTTP server and opening 1000 sockets to it, only 4 operating system threads were created:另一方面,当使用 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)
}

So, it's a mix between an IOLoop and a thread per blocking system call.因此,它是每个阻塞系统调用的 IOLoop 和线程之间的混合。

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.当 GOMAXPROCS=1 时,只有 1 个 goroutine 可以运行,无论该 goroutine 是在执行系统调用还是其他操作。

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.然而,大多数阻塞系统调用,例如套接字 I/O,在从 Go 执行时,不会在系统调用上阻塞等待定时器。 They're multiplexed by the Go runtime onto epoll, kqueue or similar facilities the OS provides for multiplexing I/O.它们由 Go 运行时多路复用到 epoll、kqueue 或操作系统为多路复用 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)对于无法与 epoll 之类的东西复用的其他类型的阻塞系统调用,Go 确实会产生一个新的 OS 线程,而不管 GOMAXPROCS 设置如何(尽管这是 Go 1.1 中的状态,我不确定情况是否改变)

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.正如您引用的文档所解释的那样,goroutine 不是线程:它只是在线程中执行的一个函数,该线程是专用的堆栈空间块。 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.因此,可以让由于某种原因(系统调用、I/O、同步)而阻塞的 goroutine 进入其线程,而其他例程可以由另一个例程执行。

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请随时在这里查看: https : //osmh.dev/posts/goroutines-under-the-hood

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

From documentation of runtime :运行时的文档:

The GOMAXPROCS variable limits the number of operating system threads that can execute user-level Go code simultaneously. GOMAXPROCS 变量限制了可以同时执行用户级 Go 代码的操作系统线程的数量。 There is no limit to the number of threads that can be blocked in system calls on behalf of Go code;代表Go代码在系统调用中可以阻塞的线程数没有限制; those do not count against the GOMAXPROCS limit.这些不计入 GOMAXPROCS 限制。

so when one OS thread is blocked for syscall, another thread can be started - and GOMAXPROCS = 1 is still satisfied.所以当一个操作系统线程被系统调用阻塞时,另一个线程可以启动 - 并且GOMAXPROCS = 1仍然满足。 The two threads are NOT running simultaneously.这两个线程没有同时运行。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM