从java的角度看golang在并发方面的特点,先从最顶层的线程模型开始,结合线程模型看golang的并发优势,最后看golang的调度细节
线程模型
- java的线程和操作系统线程是1对1的关系,启动一个线程执行任务就相当于新建了一个操作系统线程来工作。
- golang的线程模型类似java中的固定大小的线程池,现在是默认cpu核数个逻辑处理器(p),每个逻辑处理器对应一个操作系统线程(m),每个逻辑处理器中有一个队列存放要执行的goroutine(g),这就是golang中老生常谈的mpg。
- 根据上面java和golang的线程模型对比,可以看出golang的协程任务切换是用户态的操作,不像java进行线程切换是切换的的操作系统线程,有很多额外的上下文切换开销
golang并发的优势
在java web的并发世界里,影响并发性能的主要有任务调度、网络io、同步处理
- 任务调度:
- 传统java web一个请求对应一个线程(异步servlet或者非servlet容器的web flux不是这样),假设并发有2000个请求的话就对应有两千个线程处理请求,光是开辟两千个线程和调度两千个线程就已经比较费资源了
- golang的一个请求对应一个协程,同样的2000个并发请求也只是在逻辑处理器的任务队列里有2000个任务,不需要和操作系统打交道开辟线程,调度也仅仅是在用户态
- 网络io
- java标准库的网络io只有nio的底层使用的异步的系统调用,传统的bio底层调用的同步的系统调用
- golang标准库的网络io底层使用的都是异步的系统调用,只是标准库对epoll封装成了同步的调用方式
- 同步处理
- 因为java的线程模型,所以synchronized和lock等都是会把底层的操作系统线程挂起。
- golang的channel和lock等阻塞的仅仅是一个协程,相当于让逻辑处理器执行其它的协程,对操作系统线程没有影响。
golang的协程调度
- 4种时间会触发golang的协程调度:go关键字、gc、系统调用、同步处理
- go关键字,新建一个goroutine进行调度,相当于new Thread().start()
- gc,触发携程调度,开始执行gc
- 系统调用,像epoll这种异步系统调用没什么说的,逻辑处理器不会被阻塞。但是一些同步系统调用确实会阻塞住逻辑处理器对应的操作系统线程,此时该逻辑处理器队列里的其他协程会被移动到别的逻辑处理器上,不会因为一个逻辑处理器在执行同步系统调用,就把该处理器上的所有协程都阻塞住了
- 同步处理,lock和channel等同步工具会触发协程切换
好文章
有疑问加站长微信联系(非本文作者)