golang协程对比java线程

2020-11-18

并发模式

golang语言层面支持并发,这个是Go最大的特色,天生的支持并发,Go就是基因里面支持的并发,可以充分的利用多核,很容易的使用并发。

go语言支持两种并发模式,一种是Communicating Sequential Processes(CSP)模式,这种模式中值是在相互独立的协程(goroutine)中传递的,协程和协程之间通过channel进行通信。

另外一种就是我们比较传统的模式,也是我们相对熟悉的模式Share Memory Multithreading,多线程共享内存

java其实就是共享内存(共享堆空间)模式的并发模式,在涉及到多线程的问题时,必须考虑共享数据的安全性

线程实现方式

线程的实现方式主要有三种:1. 内核线程实现、2. 用户线程实现、3. 用户线程加轻量级进程混合实现

  • 内核级别线程
    内核级别线程就是直接由操作系统的内核(kernel)支持的线程,这种方式实现的线程主要通过内核的调度器来进行调度,由内核完成线程切换
    一般来讲程序不会直接调用系统内核线程,而是利用内核线程的一种高级接口-轻量级进程(Light Weight Process,即LWP,它也可以视为用户线程),也就是我们平时所说的线程,每一个LWP都是由一个内核线程支持,也就是先有内核线程,再有LWP。这种LWP与内核线程之间1:1的关系称为一对一线程模型。

  • 用户线程实现
    用户级线程主要是创建在用户空间的线程库上,系统内核感受不到线程的实现方式。用户线程的建立、同步、销毁等在用户态中完成,不需要内核的介入。这种进程和用户线程(UT)之间1:N的关系称为一对多线程模型。这种方式的优势就是上下文切换比较快,缺点是无法利用多核处理器的优势,同时调度的线程永远不会超过一个

  • 用户线程加轻量级进程混合实现
    这种方式相当于是第一种方式和第二种方式的混合,即有LWP,也有用户线程,这种方式中用户线程(UT)和LWP的数量比是不定的,即所谓的N:M关系,也就是所谓的多对多模型

Golang 对比 Java

线程和协程的区别主要是数量上的,而不是性质上,所以说协程从逻辑上来说也是线程

栈的大小

java线程栈
操作系统的线程一般都分配有一块固定大小的内存块(一般来说大小是2M,这个需要查证)。栈存储的是方法的局部变量或者一些基本数据类型(java中)。因为栈的大小是固定的,在执行某些方法的时候可能就不太够用,比如一些比较复杂或则一些深的递归操作,比如熟悉的java有时候会有栈溢出异常;当然某些时候2M可能显得有点大,这样从一定程度上来说又造成了浪费。

golang协程
协程开始的时候也会分配一定大小的内存区域,一般只有2K,和线程的栈一样,协程的栈存储的也是局部变量,但是不同的是协程的栈的大小是不固定的,是可以根据需要自动调整大小的,最大甚至可以达到1G,所以灵活性非常好

底层实现对比

常用的hotspot的JVM,采用的就是1:1的线程模型,即:map a java thread to a native thread,也就是说java线程会和native线程有个一一映射的关系,如果看下java的Thread类就可以发现有很多的native方法,这就涉及到操作系统的线程了

go语言使用了所谓的N:M调度的技术实现了自己的调度器,它在N个系统线程上多路复用(或调度)M个协程,也就是说由n个系统线程,生成了m个go的协程

go因为在创建协程的数量上一般没有特别的限制,所以可以很轻松的创建出很多个协程出来,而java因为采用的是1:1的线程模型,线程数量特别是并发线程数会受到CPU和操作系统的限制(我记得java线程池会获取当前可使用的CPU核数,可能有误),所以并发性能上应该不如go语言

参考资料

https://www.jianshu.com/p/6168b10dee34