最新公告
  • 欢迎您光临波比源码,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入我们
  • golang技术随笔(二)理解goroutine

    进程、线程和协程

    要理解甚么是goroutine,我们先来看看进程、线程和协程它们之间的区分,这能帮助我们更好的理解goroutine。

    进程:分配完全独立的地址空间,具有自己独立的堆和栈,既不同享堆,亦不同享栈,进程的切换只产生在内核态,由操作系统调度。
    线程:和其它本进程的线程同享地址空间,具有自己独立的栈和同享的堆,同享堆,不同享栈,线程的切换1般也由操作系统调度(标准线程是的)。
    协程:和线程类似,同享堆,不同享栈,协程的切换1般由程序员在代码中显式控制。

    进程和线程的切换主要依赖于时间片的控制(关于进程和线程的调度方式,具体可参看这篇文章:http://blog.chinaunix.net/uid⑵0476365-id⑴942505.html),而协程的切换则主要依赖于本身,这样的好处是避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承当调度的责任。

    goroutine可以看做是协程的go语言实现,从百度百科上看协程的定义:与子例程1样,协程(coroutine)也是1种程序组件。相对子例程而言,协程更加1般和灵活,但在实践中使用没有子例程那样广泛。实际上,我们可以把子例程当作是协程的1种特例。1般来讲,如果没有显式的让出CPU,就会1直履行当前协程。

    浅析goroutine

    我们知道goroutine是协程的go语言实现,它是语言原生支持的,相对1般由库实现协程的方式,goroutine更加强大,它的调度1定程度上是由go运行时(runtime)管理。其好处之1是,当某goroutine产生阻塞时(例犹如步IO操作等),会自动出让CPU给其它goroutine。

    goroutine的使用非常简单,例如foo是1个函数:

    go foo()

    就1个关键字go弄定了,这里会启动1个goroutine履行foo函数,然后CPU继续履行后面的代码。这里虽然启动了goroutine,但其实不意味着它会得到马上调度,关于goroutine的调度我们稍后再探讨。

    goroutine是非常轻量级的,它就是1段代码,1个函数入口,和在堆上为其分配的1个堆栈(初始大小为4K,会随着程序的履行自动增长删除)。所以它非常便宜,我们可以很轻松的创建上万个goroutine。

    go运行时调度

    默许的, 所有goroutine会在1个原生线程里跑,也就是只使用了1个CPU核。在同1个原生线程里,如果当前goroutine不产生阻塞,它是不会让出CPU时间给其他同线程的goroutines的。除被系统调用阻塞的线程外,Go运行库最多会启动$GOMAXPROCS个线程来运行goroutine。

    那末goroutine究竟是如何被调度的呢?我们从go程序启动开始说起。在go程序启动时会首先创建1个特殊的内核线程sysmon,从名字就能够看出来它的职责是负责监控的,goroutine背后的调度可以说就是靠它来弄定。

    接下来,我们再看看它的调度模型,go语言当前的实现是N:M。即1定数量的用户线程映照到1定数量的OS线程上,这里的用户线程在go中指的就是goroutine。go语言的调度模型需要弄清楚3个概念:M、P和G,以下图表示:
    这里写图片描述
    M代表OS线程,G代表goroutine,P的概念比较重要,它表示履行的上下文,其数量由$GOMAXPROCS决定,1般来讲正好等于处理器的数量。M必须和P绑定才能履行G,调度器需要保证所有的P都有G履行,以保证并行度。以下图:
    这里写图片描述
    从图中我们可以看见,当前有两个P,各自绑定了1个M,并分别履行了1个goroutine,我们还可以看见每一个P上还挂了1个G的队列,这个队列是代表私有的任务队列,它们实际上都是runnable状态的goroutine。当使用go关键字声明时,1个goroutine便被加入到运行队列的尾部。1旦1个goroutine运行到1个调度点,上下文便从运行队列中取出1个goroutine, 设置好栈和指令指针,便开始运行新的goroutine。

    那末go中切换goroutine的调度点有哪些呢?具体有以下3种情况

    • 调用runtime・gosched函数。goroutine主动放弃CPU,该goroutine会被设置为runnable状态,然后放入1个全局等待队列中,而P将继续履行下1个goroutine。使用runtime・gosched函数是1个主动的行动,1般是在履行长任务时又想其它goroutine得到履行的机会时调用。
    • 调用runtime・park函数。goroutine进入waitting状态,除非对其调用runtime・ready函数,否则该goroutine将永久不会得到履行。而P将继续履行下1个goroutine。使用runtime・park函数1般是在某个条件如果得不到满足就不能继续运行下去时调用,当条件满足后需要使用runtime・ready以唤醒它(这里唤醒以后是不是会加入全局等待队列还有待研究)。像channel操作,定时器中,网络poll等都有可能park goroutine。
    • 慢系统调用。这样的系统调用会阻塞等待,为了使该P上挂着的其它G也能得到履行的机会,需要将这些goroutine转到另外一个OS线程上去。具体的做法是:首先将该P设置为syscall状态,然后该线程进入系统调用阻塞等待。之条件到过的sysmom线程会定期扫描所有的P,发现1个P处于了syscall的状态,就将M和P分离(实际上只有当 Syscall 履行时间超越某个阈值时,才会将 M 与 P 分离)。RUNTIME会再分配1个M和这个P绑定,从而继续履行队列中的其它G。而当之前阻塞的M从系统调用中返回后,会将该goroutine放入全局等待队列中,自己则sleep去。
      这里写图片描述
      该图描写了M和P的分离进程。

    调度点的情况说清楚了,但全部模型还其实不完全。我们知道当使用go去调用1个函数,会生成1个新的goroutine放入当前P的队列中,那末甚么时候生成别的OS线程,各个OS线程又是如何做负载均衡的呢?

    当M从队列中拿到1个可履行的G后,首先会去检查1下,自己的队列中是不是还有等待的G,如果还有等待的G,并且也还有空闲的P,此时就会通知runtime分配1个新的M(如果有在睡觉的OS线程,则直接唤醒它,没有的话则生成1个新的OS线程)来分担负务。

    如果某个M发现队列为空以后,会首先从全局队列中取1个G来处理。如果全局队列也空了,则会随机从别的P那里直接截取1半的队列过来(偷窃任务),如果发现所有的P都没有可供偷窃的G了,该M就会堕入沉睡。

    全部调度模型大致就是这模样了,和所有协程的调度1样,在响应时间上,这类协作式调度是硬伤。很容易致使某个协程长时间没法得到履行。但整体来讲,它带来的好处更加让人惊叹。想要了解的更多可以看看我下面列出的1些参考资料,或是直接看它的源码:http://golang.org/src/runtime/proc.c

    总纲传送门:golang技术随笔总纲

    参考资料

    1. 协程
    2. goroutine背后的系统知识
    3. The Go scheduler
    4. goroutine与调度器
    波比源码 – 精品源码模版分享 | www.bobi11.com
    1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
    2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
    3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
    4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
    5. 如有链接无法下载、失效或广告,请联系管理员处理!
    6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需!
    7. 如遇到加密压缩包,请使用WINRAR解压,如遇到无法解压的请联系管理员!

    波比源码 » golang技术随笔(二)理解goroutine

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    波比源码
    一个高级程序员模板开发平台
    升级波友尊享更多特权立即升级