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