Goroutine的底部到底对应多少线程呢?

Go programming language 在语言层面上就支持并发,相对于Java自己手动创建线程亦或是线程池,便捷的太多太多了。

Goroutine是是一种比线程轻很多由Go自己管理不收OS调度的,类似coroutine,但又不是coroutine。

写这篇文章仅是因为在做了几个goroutine的试验之后发现了其神奇之处,所以有时间的时候一定要去看看pproc.c这个文件是如何实现的。

 是我发现启动一个go程序最少启动3个线程,比如下面这个例子(这个例子启动了4个线程)其实应该只有一个线程在运行程序至于其他的线程应该是GC的。

func main() {
	time.Sleep(time.Second * 10)
}

 启动三个线程的情况是比较特殊的一段代码,这段代码在单线程执行程序的情况下会卡死,是无法结束的。因为当前执行线程卡死在当前的goroutine中无法切换回main的goroutine,至于为什么会卡死是因为for{}无限在循环这段代码就会导致其无法切换回main goroutine,但是你可以尝试一下,如果在for{}加入一句fmt.Println("test")你就会发现线程不会卡死在当前goroutine。我猜测的原因是fmt.Println毕竟是进行了标准输出的操作虽然我们可能感觉不到,但是仍然算是一个阻塞操作,只不过这个阻塞操作的时间可以忽略不计。但是对于go的调度器就不一样了,有了这么一点阻塞的时间,go的调度器就可以重新调度main goroutine。

(这一段讨论都是基于单一线程执行代码的情况下,如果是多线程那么线程是会受os调度的所以情况就要另当别论的。)

func main() {
	go func() {
		for {
		}
	}()
	time.Sleep(time.Second * 10)
}

 go的调度器在创建线程一定会有下面的这么一个规则,在准备执行当前goroutine时,如果有空闲线程那么使用空闲线程,否则再次创建一个新的线程执行。可以举一个例子就可以证明。可以查看到该段代码会产生104个线程,因为os.Stdin.Read(blo)这一段会彻底阻塞住当前线程。所以即使有100个执行线程但每个执行线程都是阻塞住的。所以如果再开一个携程依旧是会再次在底部启动一个线程来执行。

package main

import (
	"os"
	"time"
)

func main() {
	for i := 0; i < 100; i++ {
		go func() {
			blo := make([]byte, 10)
			os.Stdin.Read(blo)
		}()
	}
	time.Sleep(time.Second * 10)
}

你可以试着for循环10000次你会发现直程序直接会会抛出异常。

 这种情况和第三种情况类似,但是换成了time.Sleep() 这里的sleep是让goroutine睡眠,而并不是线程。所以你就会发现底层启动的线程数只有4条。  

(其实在这里做过测试把Second换成了Microsecond但是我发现还只是4条线程。可能是Go的调度器对于这种冲睡眠中恢复过来的goroutine不会为其再开线程,只会利用现有的,如果没有就只能排队。) 

package main
import (
	"time"
)
func main() {
	for i := 0; i < 1000; i++ {
		go func() {
			time.Sleep(time.Second)
		}()
	}
	time.Sleep(time.Second * 10)
}

五 在这个例子中起了1000个goroutine,底部实际上启动了11条线程,我试过把1000改为1000000底部线程为192条。具体的可以自己去试试还是蛮有趣的。

(这里面其实是有闭包的,所以你会发现有值得重复。)

package main
import (
	"fmt"
	"time"
)
func main() {
	for i := 0; i < 1000; i++ {
		go func() {
			fmt.Println(i)
		}()
	}
	time.Sleep(time.Second * 10)
}

六 这个例子中你会发现线程只启动了四条,这也正是我奇怪的敌方也是我去看源码的原因,可能是因为j++不阻塞的原因吧,总之看了源码之后才知道。当然最后打印出来的结果是1000000,因为仅仅只是并发而非并行。如果想让其并行,加上这么一句 runtime.GOMAXPROCS(runtime.NumCPU()) 让go的调度器使用多核进行并行计算,你会发现这种情况下的结果是小于1000000。(当然啦你的电脑不能是单核的否则想过是一样的。)

package main
import (
	"fmt"
	"time"
)
func main() {
	j := 0
	for i := 0; i < 1000000; i++ {
		go func() {
			j++
		}()
	}
	time.Sleep(time.Second * 10)
	fmt.Println(j)
}

如果有人研究Go调度器源码,一定要经验分享出来,造福广大gopher,最近我是没时间去研究了,以后研究了,在分享些经验出来。

你可能感兴趣的:(Golang)