1 栈的大小

OS线程有固定的栈大小,一般为2MB
而对于协程来讲,通常很轻量(比如执行一个打印),不需要太大的栈,所以协程的栈大小最小为2KB
另外,协程的栈大小可以动态改变,最大1GB,以满足特殊使用需求

2 goroutine没有id

每个线程都有一个id,这个在线程创建时就会返回,所以可以很方便的通过id操作某个线程;
但是在goroutine内没有这个概念,这个是go语言设计之初考虑的,防止被滥用;
所以不能在一个协程中杀死另外一个协程,编码时需要考虑到协程什么时候创建,什么时候释放

3 调度方式

3.1 自有调度器

线程切换需要陷入内核,然后进行上下文切换,而协程在用户态由协程调度器完成,不需要陷入内核,这代价就小了;
另外,协程的切换时间点是由调度器决定的,而不是系统内核决定的,尽管他们切换点都是时间片超过一定阈值,或者进入I/O或睡眠等状态

3.2 用户线程和内核线程的映射模型

  • 一对一模型(1:1)。一个用户线程映射到一个内核线程,用户线程在存活期都会绑定到一个内核线程,一旦退出,2个线程都会退出。优点是实现了真正的并发,多个线程同时跑在不同的CPU上;缺点是,如果用户线程起多了,内核线程肯定不够用,那么就需要切换,涉及到上下文的切换,代价比较大。
  • 多对一模型(M:1)。多个用户线程映射到一个内核线程。优点是,多个用户线程切换比较快,不需要内核线程上下文切换;缺点是,如果一个线程阻塞了,那么映射到同一个内核线程的用户线程将都无法运行。
  • 多对多模型(M:N)。综合以上两种模型,go采用的就是这种,下文介绍

3.3 MPG模型

  • M:内核线程
  • P:上下文(或处理器核心,可通过GOMAXPROCS设置)
  • G:go协程

下面图我们看到他们之间的对应规则:一个M对应一个P,一个P下面挂多个G,但一个时候只有一个G在跑,其余都是放入等待队列,等待下一次切换时使用。
图片说明
那么假如一个运行的协程G调用syscall进入阻塞怎么办?如下图左边,G0进入阻塞,那么P会转移到另外一个内核线程M1(此时还是1对1)。当syscall返回后,需要抢占一个P继续执行,如果抢占不到,G0挂入全局就绪队列runqueue,等待下次调度,理论上会被挂入到一个具体P下面的就绪队列runqueu(区别于全局runqueue)。
图片说明
假如一个P0下面的所有G都跑完了,怎么办?这时候会从别的P1下面就绪队列抢占G进行运行,个数为P1就绪队列的一半。
图片说明

转载自:http://vinllen.com/gozhong-de-xie-cheng-goroutineyu-xian-cheng-de-qu-bie/