控制操作系统线程与goroutine的启动?

时间:2019-11-25 05:10:43

标签: multithreading go goroutine

Go的一大优点是它具有大量可用的轻量级线程(goroutine)。这带来了一些有趣的战略机遇,但是在某些情况下,Go倾向于生成OS线程;并且默认情况下,这些限制通常最多为10,000。如果您碰巧超过了这个数目,事情就会崩溃。

以下代码将我们的最大OS线程锁定为5,然后尝试启动10个并发的goroutine。这些goroutine将尝试使用系统调用(在这种情况下,是文件创建/写入/关闭),这往往意味着我们的goroutine将临时(且专门)与os线程关联。系统调用完成后,我们的goroutine将再次回到一个不错的轻量级线程-最有可能与其他goroutine共享os线程。

当然有一个简单的解决方案-不要那样写!实际上,如果您将文件打开/写入/关闭,一切都会正常进行...但是目前,假设下面的代码结构代表了更大,更实际的东西,实际上并不能改变很多:

package main

import (
    "fmt"
    "log"
    "os"
    "runtime/debug"
    "strconv"
    "sync"
)

func main() {
    MaxThreads := 5
    debug.SetMaxThreads(MaxThreads)

    var fileChan []chan string

    // Create 10 channels that we can use to send data to our file writers.
    for i := 0; i < 10; i++ {
        fileChan = append(fileChan, make(chan string))
    }

    var wg sync.WaitGroup

    for i := 0; i < 10; i++ {
        wg.Add(1)
        go func(n int, fChan chan string) {
            threadWriter(n, fChan)
            wg.Done()
        }(i, fileChan[i])
    }

    for i := 0; i < 10; i++ {
        for j := 0; j < 10; j++ {
            fileChan[i] <- "Test write " + strconv.Itoa(j) + " to chan " + strconv.Itoa(i)
        }
    }

    for i := 0; i < 10; i++ {
        close(fileChan[i])
    }

    wg.Wait()
    fmt.Println("All done - success")
}

func threadWriter(i int, dataChan chan string) {
    // Open the file
    f, err := os.Create("tmp/Thread-" + strconv.Itoa(i) + ".txt")
    if err != nil {
        log.Fatal("Cannot open thread file", i)
        return
    }
    // Wait for data to come in.
    for str := range dataChan {
        f.WriteString(str + "\n")
    }

    f.Close()
}

这就是问题所在:(threadtest.go) 我将运行该程序20次。有时它会正常工作,而其他时候,它会崩溃 运行时:程序超过5个线程的限制 致命错误:线程耗尽

$ go build    
$ if [ ! -d tmp ]; then mkdir tmp; fi; for i in `seq 1 20`; do ./threadtest 2>/dev/null | grep success > /dev/null; if [ $? -eq 0 ]; then echo "all good"; else echo "NOPE - Threads exhausted"; fi; rm tmp/Thread-*.txt; done
NOPE - Threads exhausted
all good
NOPE - Threads exhausted
all good
all good
all good
NOPE - Threads exhausted
NOPE - Threads exhausted
NOPE - Threads exhausted
all good
NOPE - Threads exhausted
all good
all good
all good
all good
all good
all good
all good
all good
all good

有时它可以工作,有时却不起作用-它仅取决于系统调用的时间以及当时有多少个goroutine处于“ os线程”状态。太酷了..操作系统线程必须有限制。

但是,如果没有某种方法可以检测到我们是否将要超过极限并旋转,那可能会行得通,而不是惊慌。.即:

func threadWriter(i int, dataChan chan string) {
    while(debug.OsThreadsActive() > 9999) {
      // spin here until we are safe
    }

    // Open the file
    f, err := os.Create("tmp/Thread-" + strconv.Itoa(i) + ".txt")
    if err != nil {
        log.Fatal("Cannot open thread file", i)
        return
    }
    // Wait for data to come in.
    for str := range dataChan {
        f.WriteString(str + "\n")
    }

    f.Close()
}

不幸的是,查看runtime / proc.go时,似乎没有一个可以从中获取数据的简单界面。我无法查询mcount()(实际上也不安全)。

那么-鉴于goroutine的数量超过了MaxThreads,并且有人要求在这些goroutine中使用可能阻塞的os调用,以尝试停止击中MaxThreads,有人对它有什么建议吗?或者,我是否只需要在goroutine中对任何潜在的系统调用放置一个数字限制的互斥锁,并因此尝试代表Go限制并发线程?

0 个答案:

没有答案