【问题标题】:Does OS thread get blocked on io performed by go-routine?操作系统线程是否在 go-routine 执行的 io 上被阻塞?
【发布时间】:2020-05-08 23:38:37
【问题描述】:

在我的机器上有 4 个逻辑处理器。所以有四个上下文P1P2P3P4与操作系统线程一起工作M1M2M3M4

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

操作系统线程(M1) 是否被 io(http.Get()) 上的 go-routine(G1) 阻塞?在上下文P1

Go 调度程序是否在http.Get() 上从操作系统线程(M1) 抢占 go-routine(G1)?并将G2 分配给M1...如果是,在G1 的优先权上,Goruntime 如何管理G1 以在IO 完成后恢复G1(http.Get)?

2)

检索用于每个 go-routine(G) 的上下文编号 (P) 的 api 是什么?用于调试目的..

3) 我们使用 C pthreads 库为上述读写器问题使用计数信号量维护关键部分。为什么我们不使用 go-routines 和通道来使用临界区?

【问题讨论】:

标签: c multithreading go goroutine


【解决方案1】:

不,它不会阻塞。我粗略的(并且没有来源,我是通过 osmosis 得到的)理解是,每当一个 goroutine 想要执行一个具有等效非阻塞版本的“阻塞”I/O 时,

  1. 改为执行非阻塞版本。
  2. 在表格中记录自己的 ID,该表格由它“阻止”的句柄键入。
  3. 将完成的责任转移到位于select 循环(或poll 或任何可用的等效项)中等待此类操作解除阻塞的专用线程,并且
  4. 暂停自身,释放其操作系统线程 (M) 以运行另一个 goroutine。

当 I/O 操作解除阻塞时,选择循环在表中查找哪个 goroutine 对结果感兴趣,并安排它运行。这样,等待 I/O 的 goroutine 就不会占用一个 OS 线程。

在无法以非阻塞方式完成的 I/O 或任何其他阻塞系统调用的情况下,goroutine 通过将其线程标记为阻塞的运行时函数执行系统调用,并且运行时将创建一个新的 OS 线程用于安排 goroutines。这保持了让 GOMAXPROCS 运行(未阻塞)goroutines 的能力。对于大多数程序来说,这不会导致太多的线程膨胀,因为用于处理文件、套接字等的最常见的系统调用已经变得异步友好。 (感谢@JimB 提醒我这一点,以及有用的链接答案的作者。)

【讨论】:

  • 仍有一些阻塞的 IO 调用,但它们被视为与任何其他阻塞系统调用相同;运行时会根据需要生成新线程以保持相同数量的用户线程运行。
  • 一点也不。我上面链接的问题中可能还有更多参考,我没有看得太彻底。
  • 我们使用 C pthreads 库为上述读写器问题维护带有信号量的关键部分。为什么我们不使用 go-routines 进入关键部分?
  • @overexchange:除了与手头的问题无关之外,如果您在 Go 中编写相同的代码,则需要使用相同的同步原语。但是,您正在编写不同的代码,因此比较实现细节没有用。
  • @JimB 当您说“在 Go 中编写相同的代码”时,您的意思是不使用通道吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-12-20
  • 1970-01-01
相关资源
最近更新 更多