一、单进程操作系统

单进程时代进程只能串行进行

二、多进程/多线程操作系统有了调度器

1、在多进程操作系统中,解决了阻塞问题,因为一个进程阻塞了cpu,调度器可以立刻切换到其它进程中,调度cpu算法保证了运行中的进程都被分配到cpu的运行时间片,这样从宏观来看就似乎是多个进程同时被运行

2、但新问题又出现了,进程拥有太多资源,进程创建、切换、销毁都会占用很长时间,cpu虽然利用起来了,但如果进程过多,cpu会有很大部分资源来进行调度了。会有大量的上下文切换问题,产生时间成本

三、协程

1、N个协程绑定1个线程

缺点: 某个程序用不硬件的多核能力, 一旦某协程阻塞了,本进程其它协程都无法执行

2、1个协程绑定1个线程,这种最容易实现

缺点: 协程的创建、删除和切换代价都由cpu完成,成本昂贵

3、M个协程绑定1个线程,是N:1,1:1类型的结合,客服了以上两种模型的缺点,但实现起来更复杂 区别: 协程是用户态调度实现 线程是cpu调度实现

四、go语言的协程

Go 为了提供更容易使用的并发方法

1、使用了 goroutine 和 channel。goroutine 来自协程的概念

2、即使有协程阻塞,该线程的其他协程也可以被 runtime 调度,转移到其他可运行的线程上

3、go中协程被称为 goroutine,它非常轻量,一个 goroutine 只占几 KB,并且这几 KB 就足够 goroutine 运行完

1、go通过调度器P,把可运行的goroutine分配到工作线程上

2、P的本地队列不超过256个,如果队列满了会拿出一半G,移动到全局队列中

3、M线程想运行就从P的本地队列获取G,P队列为空时就尝试从全局对队列拿一批G放到P的本地队列中

五、P和M数量

P的数量:

1、环境变量$GOMAXPROCS或者runtime的方法GOMAXPROCS()决定

M的数量:

1、go程序启动时会设置M的最大数量,默认10000,但是内核很难支持这么多线程数,所以这个限制可以忽略

2、runtime/debug中的SetMaxThreads函数,设置M的最大数量 一个M阻塞了会创建新的M

M 何时创建:

没有足够的 M 来关联 P 并运行其中的可运行的 G。 比如所有的 M 此时都阻塞住了,而 P 中还有很多就绪任务,就会去寻找空闲的 M,而没有空闲的,就会去创建新的 M。

六、调度器策略

复用线程:避免频繁的创建、销毁线程、而是对线程复用

1、work stealing机制

当本线程无可运行的G时,尝试从其它线程绑定的P偷取G,而不是销毁线程

2、hand off机制

当本线程因为G阻塞时,该线程释放绑定的P,把P转移给其它空闲的线程执行。

并行:GOMAXPROCS设置P的数量,最多有GOMAXPROCS个线程分布在多个CPU上同时运行。

比如:GOMAXPROCS=核数/2,则最多利用了一半的CPU核进行并行