Multithreading 当许多goroutine在golang中写入文件时被阻止,为什么它不创建许多线程?

Multithreading 当许多goroutine在golang中写入文件时被阻止,为什么它不创建许多线程?,multithreading,go,concurrency,goroutine,Multithreading,Go,Concurrency,Goroutine,正如我们在go中所知道的,当goroutine必须执行阻塞调用(例如系统调用)或通过cgo调用C库时,可能会创建一个线程。一些测试代码: package main import ( "io/ioutil" "os" "runtime" "strconv" ) func main() { runtime.GOMAXPROCS(2) data, err := ioutil.

正如我们在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 {}
    }
有什么想法吗?

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

从包装文件中引用:

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

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

博客文章将其定义为:

它们被称为goroutines,因为现有的术语threads、corroutine、processs等等传达了不准确的含义。goroutine有一个简单的模型:它是一个与同一地址空间中的其他goroutine并发执行的函数。它是轻量级的,只需分配堆栈空间。堆栈开始时很小,所以很便宜,并通过根据需要分配(和释放)堆存储来增长

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

讨论如何限制创建的实际线程(而不是goroutine)的数量

Go 1.2在中引入了线程限制管理

您可以查看一个测试,以检查在以下情况下是否实际达到创建的线程数:

该文件有一个示例,用于创建实际线程(针对给定的goroutine),使用:

func testinewthread(名称字符串){
c:=制造(陈布尔)
go func(){
runtime.LockOSThread()
测试(名称)

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

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

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

语句
并且由于您显式地将GOMAXPROCS设置为2,您不能期望它产生200个线程。
是不正确的。每个阻塞的系统调用都可以创建一个新线程。@NickCraig Wood您能看看吗?谢谢ou,这对我很有帮助。我犯了一个错误,在我的例子中,我用
go run
运行程序。@frank.lin这是什么意思?构建和运行二进制文件有什么不同吗?
所以我认为在最初的测试中,系统调用退出得太快,无法显示您期望的效果。
这是否意味着即使对于IO操作,仍然有可能
P
不会从当前
M
中分离出来,或者其他一些不会产生新线程的东西?@atline是的,关于这个问题你是对的。这是一篇非常好的文章,它帮助我解释了为什么goroutine中有一个不确定的for循环?如果没有它,代码也会产生相同的输出.
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)
    }
}
func testInNewThread(name string) {
    c := make(chan bool)
    go func() {
        runtime.LockOSThread()
        test(name)
        c <- true
    }()
    <-c
}
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 {}
}
$ cat /proc/17033/status | grep -i thread
Threads:    203