繁体   English   中英

当在golang中写入文件时阻塞了许多goroutine时,为什么不创建多个线程?

[英]Why does it not create many threads when many goroutines are blocked in writing file in golang?

正如我们在go中所知,当goroutine必须执行阻塞调用(例如系统调用)或通过cgo调用C库时,可能会创建一个线程。 一些测试代码:

   package main

   import (
        "io/ioutil"
        "os"
        "runtime"
        "strconv"
    )

    func main() {
        runtime.GOMAXPROCS(2)
        data, err := ioutil.ReadFile("./55555.log")
        if err != nil {
            println(err)
            return
        }
        for i := 0; i < 200; i++ {
            go func(n int) {
                for {
                    err := ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm)
                    if err != nil {
                        println(err)
                        break
                    }
                }
            }(i)
        }
        select {}
    }

当我运行它时,它没有创建很多线程。

➜ =99=[root /root]$ cat /proc/9616/status | grep -i thread
Threads:    5

有任何想法吗?

我稍微修改了你的程序以输出更大的块

package main

import (
    "io/ioutil"
    "os"
    "runtime"
    "strconv"
)

func main() {
    runtime.GOMAXPROCS(2)
    data := make([]byte, 128*1024*1024)
    for i := 0; i < 200; i++ {
        go func(n int) {
            for {
                err := ioutil.WriteFile("testxxx"+strconv.Itoa(n), []byte(data), os.ModePerm)
                if err != nil {
                    println(err)
                    break
                }
            }
        }(i)
    }
    select {}
}

然后按预期显示> 200个线程

$ cat /proc/17033/status | grep -i thread
Threads:    203

所以我认为系统调用在原始测试中退出太快,以显示您期望的效果。

goroutine是一个轻量级线程,它不等同于操作系统线程。 语言规范将其指定为“在同一地址空间内的独立并发控制线程”

引用包runtime的文档:

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

仅仅因为你启动200个goroutines,它并不意味着将为它们启动200个线程。 您将GOMAXPROCS设置为2,这意味着可以同时运行2个“活动”goroutine。 如果goroutine被阻止(例如I / O等待),则可能会产生新线程。 你没有提到你的测试文件有多大,你开始的goroutines可能会写得太快。

Effective Go博客文章将它们定义为:

它们被称为goroutines,因为现有的术语 - 线程,协同程序,进程等 - 传达了不准确的内涵。 goroutine有一个简单的模型:它是一个与同一地址空间中的其他goroutine同时执行的函数。 它是轻量级的,比堆栈空间的分配花费更多。 并且堆栈开始很小,因此它们很便宜,并且通过根据需要分配(和释放)堆存储来增长。

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

问题4056讨论了如何限制创建的实际线程(不是goroutine)的数量。

Go 1.2在commit 665feee中介绍了线程限制管理。

您可以在pkg/runtime/crash_test.go#L128-L134查看测试以检查是否实际创建了创建的线程数:

func TestThreadExhaustion(t *testing.T) {
    output := executeTest(t, threadExhaustionSource, nil)
    want := "runtime: program exceeds 10-thread limit\nfatal error: thread exhaustion"
    if !strings.HasPrefix(output, want) {
        t.Fatalf("output does not start with %q:\n%s", want, output)
    }
}

同一个文件有一个使用runtime.LockOSThread()创建实际线程(对于给定的goroutine)的示例:

func testInNewThread(name string) {
    c := make(chan bool)
    go func() {
        runtime.LockOSThread()
        test(name)
        c <- true
    }()
    <-c
}

暂无
暂无

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

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