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

时间:2015-01-28 06:56:36

标签: multithreading go concurrency goroutine

正如我们所知,当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

有什么想法吗?

3 个答案:

答案 0 :(得分:6)

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

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 {}
}

然后按预期显示&gt; 200个线程

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

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

答案 1 :(得分:4)

goroutine是轻量级线程,它不等同于操作系统线程。 Language specification将其指定为同一地址空间内的&#34;独立并发控制线程&#34;

引用包runtime的文档:

  

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

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

Effective Go博客文章将其定义为:

  

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

     

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

答案 2 :(得分:1)

issue 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
}