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

Concurrency goroutines是如何工作的?(或:goroutines和OS线程关系),concurrency,go,goroutine,Concurrency,Go,Goroutine,在调用系统调用时,其他goroutine如何保持执行?(使用GOMAXPROCS=1时) 据我所知,在调用系统调用时,线程会放弃控制,直到系统调用返回。 如何在不为syscall goroutine上的每个阻塞创建系统线程的情况下实现这种并发性 从: 戈罗季斯 它们被称为goroutines,因为现有的术语是线程, 协同程序、过程等传达了不准确的内涵。A. goroutine有一个简单的模型:它是一个并发执行的函数 与同一地址空间中的其他goroutine。它很轻, 只需分配堆栈空间即可。还有书

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

从:

戈罗季斯

它们被称为goroutines,因为现有的术语是线程, 协同程序、过程等传达了不准确的内涵。A. goroutine有一个简单的模型:它是一个并发执行的函数 与同一地址空间中的其他goroutine。它很轻, 只需分配堆栈空间即可。还有书堆 从小处做起,这样他们就便宜了,然后通过分配(和释放)来增长 根据需要堆存储

goroutine被多路复用到多个OS线程上,因此如果需要 块,例如在等待I/O时,其他块继续运行。他们的 设计隐藏了线程创建和创建的许多复杂性 管理层


不可能。当GOMAXPROCS=1时,一次只能运行一个goroutine,无论该goroutine正在执行系统调用还是其他操作

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


对于其他类型的阻塞系统调用,如果无法与epoll等进行多路复用,则无论GOMAXPROCS设置如何,Go都会生成一个新的OS线程(尽管这是Go 1.1中的状态,但我不确定情况是否发生了变化)

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


参考资料:

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


正如您引用的文档所解释的,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
        }
}

因此,每次阻塞系统调用都是一个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)
}
主程序包
输入“fmt”

func put(number chan我写了一篇文章,解释了它们是如何使用示例和图表的。请在这里随意查看:

来自以下文档:

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


因此,当一个OS线程被syscall阻止时,另一个线程可以启动-并且仍然满足
GOMAXPROCS=1
的要求。这两个线程没有同时运行。

我看不出有任何理由投反对票。这个答案很好地总结了这一点。这是因为
GOMAXPROCS
无法处理可以启动的Goroutine的数量运行。至少,不是在问题的上下文中。而且,
GOMAXPROCS
并没有阻止go创建更多线程。它只是阻止go在多个处理器上执行它们…@Elwinar
GOMAXPROCS
设置同时运行go代码的系统线程数。因为每个go代码都与一个goroutine关联,所以这一效果客观地限制了同时运行go例程的数量。此外,nos很好地解释了,即使
GOMAXPROCS
为1,也可以有多个系统线程。此外,处理器与此无关。这个答案帮助我理解了IOLoop的存在,但这不是问题,所以我不接受它。投票赞成anywaY:)谢谢!它对套接字使用本机epoll(或其他系统上的altentive),而不将其用于文件io,在邮件列表的某个地方有一个关于它的讨论。Go运行时有一个叫做网络轮询器的东西,它使用目标操作系统上可用的非阻塞本机API来处理网络IO,这样您就可以拥有所有使用网络IO的Goroutine的权限。非阻塞I/O并不是什么新鲜事。Node.js或Java NIO或许多其他语言通过利用操作系统提供的调用来支持它。它实际上将不同的goroutine多路复用到线程中(每个线程1+goroutine)。如果一个线程阻塞,另一个线程将切换为在同一线程上运行。这个操作只需要3个寄存器就可以更改,这就是为什么它这么轻的原因。Dave Cheney's的这篇博客文章真正解释了相关内容,并包含了一些来自较新Go scheduler的图表:
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)
}