我认识一位很是精通golang编程技巧的工程师。他/她经验丰富,擅长各类解决工程问题的技法,对系统了解也极为深刻。遇到golang的实战问题,他/她每每能够一语中的,谈笑间bug灰飞烟灭。javascript
这么一位值得尊敬的工程师,在别人问他golang的goroutine是个啥玩意的时候,他/她瞠目结舌,不知道该怎么跟对方解释好,竟然说:“goroutine就有点像java的线程池啦。” excuse me!这也太狗屁不通了吧!java
因此我以为,我来装出一副我比他/她更懂的姿态,给你们科普一下什么是goroutine。对goroutine了如指掌的同窗请绕行。python
要了解啥是goroutine,咱们得先了解啥是coroutine。(不了解coroutine的同窗请举起腿来!---郭德纲)c++
coroutine也就是协程。程序员
要了解什么是协程,咱们先得了解他的特殊形式:例程。golang
一个不built in支持协程的语言,写出来的函数咱们叫subroutine,或者叫例程。 subroutine A 调用 subroutine B 意味着在A的stack里边开创一片空间来做为B的stack。B里边能够继续调用C,C里边能够继续调用D... 要注意的是,全部后面被调用的家伙都会共享A的线程开辟的stack空间。若是咱们写了一个调用嵌套特别复杂的函数,咱们颇有可能看见StackOverFlow! 固然若是咱们写一个A调用B,B里边再调用A这样的子子孙孙无穷尽的函数调用,咱们更容易碰到StackOverFlow!编程
例程基本讲完了。bash
c/c++/java 不加上一些特殊库的支持的话,咱们写的函数调用方式都是把对方当作例程来的。函数
咱们能够很容易推断出来,在一个线程里边,调用者例程必定是要等到被调用的例程执行完成而且返回才能继续执行。好比:oop
public static void funcionA(){
int resultFromB = functionB();
System.out.println("B returned : " + resultFromB);
}
复制代码
而被调用的例程里边若是调用了调用者例程的话,也是从新开一个function stack来执行的。好比上面的栗子:若是functionB里边调用了functionA(好吧,我知道这么写的人是大sb),那么另外一个functionA的stack会被建立,而后执行。
可是coroutine呢?
var q := new queue
coroutine produce
loop
while q is not full
create some new items
add the items to q
yield to consume
coroutine consume
loop
while q is not empty
remove some items from q
use the items
yield to produce
复制代码
coroutine produce和consume可使用yield关键字把执行权推来推去。咱们在这个例子里边能够直白的把yield理解为:我先歇歇。
produce向q里边丢了东西,而后表示它要歇歇,让consume干会儿活。
consume用了q里边的东西,而后表示它要歇歇,让produce干会儿活。
produce和consume不是互为subroutine,互相的stack也是独立的。
假如produce不使用yield关键字,直接调用consume,那就变成了subroutine的调用了。 因此咱们说,subroutine是coroutine的特殊形式。
咱们来看看goroutine:
func main(){
ch:=make(chan int)
go routineA(ch)
go routineB(ch)
println("goroutines scheduled!")
<-ch
<-ch
}
func routineA(ch chan int){
println("A executing!")
ch<-1
}
func routineB(ch chan int){
println("B executing!")
ch<-2
}
复制代码
go这个关键字很是有用!他的意思是:滚!
routineA 滚开,而后执行!
routineB 滚开,而后执行!
咱们看到,main函数这个goroutine里边打开了两个新的goroutine,而且要求他们滚开去找个时间执行本身。咱们能够断言:"goroutines scheduled!"这行字将会先被输出到console。而”A/B executing!“则会晚一些才输出。 那么问题来了,A和B啥时候才能获得执行机会呢?
答案:当正在执行的goroutine遇到系统IO(timer,channel read/write,file read/write...)的时候,go scheduler会切换出去看看是否是有别的goroutine能够执行一把,这个时候A和B就有机会了。实际上,这就是golang协程的概念。同时用少数的几个线程来执行大量的goroutine协程,谁正在调用系统IO谁就歇着,让别人用CPU。
因此若是咱们用pprof看你的服务,可能发现有几千条goroutine,可是真正运行的线程只有小猫两三只。
引伸问题:假如我写个不作任何系统IO的函数会怎么样?
func noIO(){
go routineA()
go routineB()
for {
println("i will never stop!")
}
}
复制代码
go scheduler 专门对此做了处理。若是是早期的go版本,你将会看到大量的"i will never stop!",而且发现routineA和B没啥执行机会。如今go1.9会怎么样,各位童鞋不放举起腿来本身试试看。
因此综上所述:golang里边使用go 这个很是关键的关键字,来触发协程调度。 相比python等语言对协程的支持,golang的支持是很是傻瓜友好的。好比python的
yield
await
run_until_complete
复制代码
分分钟能够弄晕你。
但愿这篇文章能对你有点小用处。向小白介绍goroutine的时候,我以为能够这样: goroutine有点像是light weight的线程。一个真正的线程能够调度不少goroutine,不一样的goroutine能够被挂载在不一样线程里边去执行。这些都是自动的,对程序员很友好。
题外话,咱们能够设置系统里边只有一条线程,全部的goroutine都在这一条线程上面跑。那么咱们能够省掉一个很恶心的东西:
对的,是sync.RWMutex.