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限制并发线程?