美文网首页
java线程和go协程

java线程和go协程

作者: 非典型_程序员 | 来源:发表于2018-11-17 14:19 被阅读0次

一、线程的实现

线程的实现方式主要有三种:内核线程实现、用户线程实现、用户线程加轻量级进程混合实现。因为自己只对java的线程比较熟悉一点,所以主要针对java线程和go的协程之间进行一个对比。
线程模型主要有三种:1、内核级别线程;2、用户级别线程;3、混合线程
1、内核级别线程
内核级别线程就是直接由操作系统的内核(kernel)支持的线程,这种方式实现的线程主要通过内核的调度器来进行调度,由内核完成线程切换。一般来讲程序不会直接调用系统内核线程,而是利用内核线程的一种高级接口-轻量级进程(Light Weight Process,即LWP,它也可以视为用户线程),也就是我们平时所说的线程,每一个LWP都是由一个内核线程支持,也就是先有内核线程,再有LWP。这种LWP与内核线程之间1:1的关系称为一对一线程模型,这是一种最简单的线程实现方式。见下图:


2018-11-16 22-40-01 的屏幕截图.png

这种方式创建的每一个线程都需要由一个内核线程支持,需要消耗一定的内核资源,因此一个系统支持的线程数量是有限的。另外由于基于内核线程实现,这种方式创建的线程操作需要进行系统调用,而系统调用代价较高,需要在用户态和内核态进行切换(这个我也不懂)。
2、用户级别线程
这里所指的用户级线程主要是创建在用户空间的线程库上,系统内核感受不到线程的实现方式。用户线程的建立、同步、销毁等在用户态中完成,不需要内核的介入。这种进程和用户线程(UT)之间1:N的关系称为一对多线程模型。


1321313.jpg
这种方式的优势就是上下文切换比较快,缺点是无法从多线程处理器或多处理器计算机上的硬件加速中受益,同时调度的线程永远不会超过一个。
3、混合线程
这种方式相当于是第一种方式和第二种方式的混合,即有LWT,也有用户线程,这种方式中用户线程(UT)和LWT的数量比是不定的,即所谓的N:M关系,也就是所谓的多对多模型。这种实现线程的方式相比前两种也更为复杂,这种方式中由线程库负责在可用的可调度实体上调度用户线程,这使得线程的上下文切换非常快,因为它避免了系统调用。但是增加了复杂性和优先级倒置的可能性,以及在用户态调度程序和内核调度程序之间没有广泛(且高昂)协调的次优调度。
3213213123.jpg

java线程实现

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

二、go语言并发模式

go语言支持两种并发模式,一种是Communicating Sequential Processes(CSP)模式,这种模式中值是在相互独立的协程(goroutine)中传递的,协程和协程之间使用到就是上次说到的channel。另外一种就是我们比较传统的模式,也是我们相对熟悉的模式Share Memory Multithreading。但是go语言推荐的还是第一种模式,go官网文档是说:Do not communicate by sharing memory; instead, share memory by communicating.也就是说不建议线程或协程之间通过共享内存通讯,而是通过通讯共享内存。比方说比较熟悉的java其实就是共享内存模式的并发模式,在涉及到多线程的问题时,必须考虑共享数据的安全性。

三、线程和协程之间的区别

这里说的协程指的只是go语言的goroutine。线程和协程的区别主要是数量上的,而不是性质上,所以说协程从逻辑上来说也是线程。

栈的大小:

1、线程栈
操作系统的线程一般都分配有一块固定大小的内存块(一般来说大小是2M,这个需要查证),我查找资料显示的64位Linux上,hotspot虚拟机的栈的大小默认为1M,地址:https://www.oracle.com/technetwork/java/javase/tech/vmoptions-jsp-140102.html。。栈存储的是方法的局部变量或者一些基本数据类型(java中)。因为栈的大小是固定的,在执行某些方法的时候可能就不太够用,比如一些比较复杂或则一些深的递归操作,比如熟悉的java有时候会有栈溢出异常;当然某些时候2M可能显得有点大,这样从一定程度上来说又造成了浪费。
2、协程
协程开始的时候也会分配一定大小的内存区域,一般只有2K,和线程的栈一样,协程的栈存储的也是局部变量,但是不同的是协程的栈的大小是不固定的,是可以根据需要自动调整大小的,最大甚至可以达到1G,所以灵活性非常好。

调度方式

1、系统级别的线程是由操作系统的内核(kernel)调度的,每过几毫秒,硬件的计时器就会中断处理器,从而引起被成为调度器的内核函数执行。调度器会暂停当前正在执行的线程,并把它的寄存器存到内存中,并查看线程列表决定接下来执行哪一个线程,然后从内存中恢复改线程的寄存器,最后恢复该线程的执行。因为线程是通过内核调度的,从一个线程切换到另一个线程就涉及到上下文转换,建议看下维基百科:https://en.wikipedia.org/wiki/Context_switch。简单说就是将一个用户线程的状态保存到内存,恢复另一个用户线程的状态,并且更新调度程序的数据结构,导致上下文切换比较慢。
2、go语言使用了所谓的N:M调度的技术实现了自己的调度器,它在N个系统线程上多路复用(或调度)M个协程,也就是说由n个系统线程,生成了m个go的协程(可以这么理解吧?)。go语言的调度工作类似于系统内核的调度,但是它只关注单个go程序的协程。
另外一点就是go的协程是没有标识的,在java中当前执行的线程都会有一个唯一的标识,它的好处就是可以很容易的就构建出一个抽像的"thread-local storage",即java中的ThreadLocal,每个线程都可以创建出这样一个数据结构存储只属于当前线程的一些变量。但是goroutine并不支持,因为ThreadLocal可能会被滥用。go语言提倡的是一种更简单的编程方式,即参数影响函数的行为应该是显性的。

go因为在创建协程的数量上一般没有特别的限制,所以可以很轻松的创建出很多个协程出来,而java因为采用的是1:1的线程模型,线程数量特别是并发线程数会受到CPU和操作系统的限制(我记得java线程池会获取当前可使用的CPU核数,可能有误),所以并发性能上应该不如go语言,有人也说go语言天生就带有高并发光环加持。这里无意区分java和go孰优孰劣,只是想从线程和协程的实现上来简单的了解下二者的差别。


最后:自己在微信开了一个个人号:超超学堂,都是自己之前写过的一些文章,另外关注还有Java免费自学资料,欢迎大家关注。

二维码.jpg

相关文章

  • 2021/04/07GO语句的执行规则

    1.理解协程是如何被调度的 java中的线程和系统线程的调度关系为1:1go中协程和系统线程的调度关系为M:N,这...

  • java线程和go协程

    一、线程的实现 线程的实现方式主要有三种:内核线程实现、用户线程实现、用户线程加轻量级进程混合实现。因为自己只对j...

  • Go协程介绍

    参考自《go专家编程》Go协程所实现的是M:N的线程模型,M个协程运行在N个线程中。 1. MPG模型 Go协程中...

  • GO学习笔记(18) - 并发编程(1) - 理解gorouti

    目录 进程、线程与协程 并发模模型介绍 GO并发编程介绍 进程、线程与协程 进程和线程 进程是程序在操作系统中的一...

  • Go语言并发学习笔记

    一、Go程 1.goroutine是由 Go 运行时管理的轻量级线程 我们称之为Go程是因为现有的术语—线程、协程...

  • misc.

    1. linux常用命令 top 2. go的协程与线程的区别 线程是操作系统的调度单元 协程是由go的runti...

  • Go调度相关

    go 调度go routinue在线程中进行调度 GPM的概念: G(Goroutine): 即Go协程,每个go...

  • 了解Kotlin协程你需要加深了解的 Java 线程

    协程铺垫知识 —— JAVA 线程 Question: 线程和进程的区别?cpu 个数、cpu 核心数、cpu ...

  • C++程序员的go学习之路(3)——goroutine、chan

    goroutine go中的并发主要靠协程(goroutine)。不同于C和C++中常用的多线程,协程并不与操作系...

  • Linux高性能网络:协程系列01-概述

    协程 我们只听说过进程和线程,没有听说过协程,协程是个什么东西呢?我们的go语言,node.js语言的实现都使用到...

网友评论

      本文标题:java线程和go协程

      本文链接:https://www.haomeiwen.com/subject/bhsufqtx.html