OS 线程是否在 go-routine 执行的 io 上被阻塞?
Does OS thread get blocked on io performed by go-routine?
我的机器上有 4 个逻辑处理器。所以有四个上下文 P1
、P2
、P3
和 P4
使用 OS 个线程 M1
、M2
、M3
& M4
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
在下面的代码中:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func getPage(url string) (int, error) {
resp, err := http.Get(url)
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, err
}
return len(body), nil
}
func worker(urlChan chan string, sizeChan chan<- string, i int) {
for {
url := <-urlChan
length, err := getPage(url)
if err == nil {
sizeChan <- fmt.Sprintf("%s has length %d (%d)", url, length, i)
} else {
sizeChan <- fmt.Sprintf("%s has error %s (%d)", url, err, i)
}
}
}
func main() {
urls := []string{"http://www.google.com/", "http://www.yahoo.com",
"http://www.bing.com", "http://bbc.co.uk", "http://www.ndtv.com", "https://www.cnn.com/"}
urlChan := make(chan string)
sizeChan := make(chan string)
for i := 0; i < len(urls); i++ {
go worker(urlChan, sizeChan, i)
}
for _, url := range urls {
urlChan <- url
}
for i := 0; i < len(urls); i++ {
fmt.Printf("%s\n", <-sizeChan)
}
}
有六个 go-routines 执行 http.Get()
1)
OS thread(M1
) 是否被 io(http.Get()
) 上的 go-routine(G1
) 阻塞?根据上下文 P1
或
Go 调度程序是否会在 http.Get()
上抢占来自 OS 线程(M1
)的 go-routine(G1
)?并将 G2
分配给 M1
... 如果是,在抢占 G1
时,Goruntime 如何管理 G1
在完成后恢复 G1
IO(http.Get
)?
2)
检索用于每个 go-routine(G) 的上下文编号(P) 的 api 是什么?用于调试目的..
3) 对于上述 reader writer 问题,我们使用计数信号量维护临界区,使用 C pthreads 库。为什么我们不使用 go-routines 和 channels 进入关键部分的使用?
不,它不会阻塞。我粗略的(并且没有来源,我通过渗透获得它)理解是每当一个 goroutine 想要执行一个 "blocking" I/O 具有等效的非阻塞版本时,
- 改为执行非阻塞版本。
- 将自己的 ID 记录在 table 某处,由 "blocking" 所在的句柄键入。
- 将完成的责任转移到位于
select
循环(或 poll
或任何可用的等效物)中的专用线程,等待此类操作解除阻塞,并且
- 暂停自身,将其 OS 线程 (M) 释放给 运行 另一个 goroutine。
当 I/O 操作解除阻塞时,select-loop 在 table 中查找以找出哪个 goroutine 对结果感兴趣,并将其调度为 运行.这样,等待 I/O 的 goroutines 不占用 OS 线程。
如果 I/O 无法以非阻塞方式完成,或任何其他阻塞系统调用,goroutine 通过 运行time 函数执行系统调用,将其线程标记为阻塞,并且 运行time 将创建一个新的 OS 线程来安排 goroutines。这保持了拥有 GOMAXPROCS 运行ning(未阻塞)goroutines 的能力。对于大多数程序来说,这不会导致太多的线程膨胀,因为处理文件、套接字等的最常见的系统调用已经变得异步友好。 (感谢@JimB 提醒我这一点,以及有用的链接答案的作者。)
我的机器上有 4 个逻辑处理器。所以有四个上下文 P1
、P2
、P3
和 P4
使用 OS 个线程 M1
、M2
、M3
& M4
$ lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 4
On-line CPU(s) list: 0-3
Thread(s) per core: 2
Core(s) per socket: 2
Socket(s): 1
在下面的代码中:
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func getPage(url string) (int, error) {
resp, err := http.Get(url)
if err != nil {
return 0, err
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return 0, err
}
return len(body), nil
}
func worker(urlChan chan string, sizeChan chan<- string, i int) {
for {
url := <-urlChan
length, err := getPage(url)
if err == nil {
sizeChan <- fmt.Sprintf("%s has length %d (%d)", url, length, i)
} else {
sizeChan <- fmt.Sprintf("%s has error %s (%d)", url, err, i)
}
}
}
func main() {
urls := []string{"http://www.google.com/", "http://www.yahoo.com",
"http://www.bing.com", "http://bbc.co.uk", "http://www.ndtv.com", "https://www.cnn.com/"}
urlChan := make(chan string)
sizeChan := make(chan string)
for i := 0; i < len(urls); i++ {
go worker(urlChan, sizeChan, i)
}
for _, url := range urls {
urlChan <- url
}
for i := 0; i < len(urls); i++ {
fmt.Printf("%s\n", <-sizeChan)
}
}
有六个 go-routines 执行 http.Get()
1)
OS thread(M1
) 是否被 io(http.Get()
) 上的 go-routine(G1
) 阻塞?根据上下文 P1
或
Go 调度程序是否会在 http.Get()
上抢占来自 OS 线程(M1
)的 go-routine(G1
)?并将 G2
分配给 M1
... 如果是,在抢占 G1
时,Goruntime 如何管理 G1
在完成后恢复 G1
IO(http.Get
)?
2)
检索用于每个 go-routine(G) 的上下文编号(P) 的 api 是什么?用于调试目的..
3) 对于上述 reader writer 问题,我们使用计数信号量维护临界区,使用 C pthreads 库。为什么我们不使用 go-routines 和 channels 进入关键部分的使用?
不,它不会阻塞。我粗略的(并且没有来源,我通过渗透获得它)理解是每当一个 goroutine 想要执行一个 "blocking" I/O 具有等效的非阻塞版本时,
- 改为执行非阻塞版本。
- 将自己的 ID 记录在 table 某处,由 "blocking" 所在的句柄键入。
- 将完成的责任转移到位于
select
循环(或poll
或任何可用的等效物)中的专用线程,等待此类操作解除阻塞,并且 - 暂停自身,将其 OS 线程 (M) 释放给 运行 另一个 goroutine。
当 I/O 操作解除阻塞时,select-loop 在 table 中查找以找出哪个 goroutine 对结果感兴趣,并将其调度为 运行.这样,等待 I/O 的 goroutines 不占用 OS 线程。
如果 I/O 无法以非阻塞方式完成,或任何其他阻塞系统调用,goroutine 通过 运行time 函数执行系统调用,将其线程标记为阻塞,并且 运行time 将创建一个新的 OS 线程来安排 goroutines。这保持了拥有 GOMAXPROCS 运行ning(未阻塞)goroutines 的能力。对于大多数程序来说,这不会导致太多的线程膨胀,因为处理文件、套接字等的最常见的系统调用已经变得异步友好。 (感谢@JimB 提醒我这一点,以及有用的链接答案的作者。)