纤程与Quasar

Java使用的是系统级线程,也就是说,每次调用new Thread(....).run(),都会在系统层面建立一个新的线程,然鹅新建线程的开销是很大的(每个线程默认情况下会占用1MB的内存空间,当然你愿意的话可以用-Xss来调小点),更不要说线程切换带来的开销了

为了节省开销,程序员玩出了很多花样。

最常用的是线程池(线程复用,但是完全无法处理阻塞调用的问题)

以及事件驱动框架(NIO或者Netty,用少量的工作线程来服务大量的慢速IO连接,但是EventLoop中也不能有阻塞调用,耗时的逻辑必须放在额外的线程池里处理)

但是NIO的代码难写也难懂,像我这种懒惰的程序猴子,最喜欢的还是一个线程对应一个连接这种简单粗暴的编程手法。

纤程(Coroutine)是我们的救星

所谓的纤程,或者协程,可以理解为是一种轻量级的线程,它与线程的主要区别在于

a. 线程切换的过程是由系统内核完成,切换的过程中会进入到内核态。而纤程则完全工作在用户态。

b. 线程是否发生切换是由操作系统决定的(抢占式调度),工作线程本身没有决定权。而纤程的切换是需要工作纤程主动放弃CPU,这样调度器才能让另外一个纤程继续运行。

很多语言已经内置了纤程,最著名的应该就是Go了,用go关键字,就能直接创建一个纤程并在其中为所欲为,其他的Scheduler会自动帮你搞定。所以Go能相对容易的写出正确的高并发程序。

可惜的是,Java没有官方的纤程支持,好在有个叫做Quasar的库可堪一用

使用这个lib,你就能在Java程序中创建纤程了,代码大概长这个样子:

    public static void main(String[] args)
            throws ExecutionException, InterruptedException, SuspendExecution {
        int FiberNumber = 1_000_000;
        CountDownLatch latch = new CountDownLatch(1);
        AtomicInteger counter = new AtomicInteger(0);

        for (int i = 0; i < FiberNumber; i++) {
            new Fiber(() -> {
                counter.incrementAndGet();
                if (counter.get() == FiberNumber) {
                    System.out.println("done");
                }
                Strand.sleep(1000000);
            }).start();
        }
        latch.await();
    }

在上面这段代码中,我们直接创建了一百万个纤程,如果是一般的Thread,不考虑OS能否负担得起,单单占用的内存就要1T起步。

但是这段程序实际占用的内存只在1G出头,也就是说每个纤程的内存占用只在1K左右。

这是如何做到的?

Quasar在编译时会对代码进行扫描,如果方法带有Suspendable注解,或者抛出SuspendExecution,或者在配置文件中被指定,Quasar会直接修改生成的字节码,在park方法的前后,插入一些字节码。

这些字节码会记录此时纤程的执行状态(相关的局部变量与操作数栈),然后通过抛出异常的方式将CPU的控制权从当前协程交回到控制器

此时控制器可以再次调度另外一个纤程运行,并通过之前插入的那些字节码恢复当前纤程的执行状态,使程序能继续正常执行。

并且,这些操作是非常轻量的,所以内存消耗极小,也不会对CPU带来太多的额外开销(据说在3%-5%)

参考资料

Coroutine in Java - Quasar Fiber实现

次时代Java编程(一):Java里的协程

时间: 2024-10-03 22:37:24

纤程与Quasar的相关文章

Java 中的纤程库 – Quasar

来源:鸟窝, colobu.com/2016/07/14/Java-Fiber-Quasar/ 如有好文章投稿,请点击 → 这里了解详情 最近遇到的一个问题大概是微服务架构中经常会遇到的一个问题: 服务 A 是我们开发的系统,它的业务需要调用 B.C.D 等多个服务,这些服务是通过http的访问提供的. 问题是 B.C.D 这些服务都是第三方提供的,不能保证它们的响应时间,快的话十几毫秒,慢的话甚至1秒多,所以这些服务的Latency比较长.幸运地是这些服务都是集群部署的,容错率和并发支持都比较

Java的纤程库 - Quasar

最近遇到的一个问题大概是微服务架构中经常会遇到的一个问题: 服务 A 是我们开发的系统,它的业务需要调用 B . C . D 等多个服务,这些服务是通过http的访问提供的. 问题是 B . C . D 这些服务都是第三方提供的,不能保证它们的响应时间,快的话十几毫秒,慢的话甚至1秒多,所以这些服务的Latency比较长.幸运地是这些服务都是集群部署的,容错率和并发支持都比较高,所以不担心它们的并发性能,唯一不爽的就是就是它们的Latency太高了. 系统A会从Client接收Request,

第12章 纤程(Fiber)

12.1 纤程对象的介绍 (1)纤程与线程的比较 比较 线程(Thread) 纤程(Fiber) 实现方式 是个内核对象 在用户模式中实现的一种轻量级的线程,是比线程更小的调度单位. 调度方式 由Microsoft定义的算法来调度,操作系统对线程了如指掌.内核对线程的调度是抢占式的. 由我们自己调用SwitchToFiber来调度,内核对纤程一无所知.线程一次只能执行一个纤程代码,纤程间的调度不是抢占式的. 备注 ①一个线程可以包含一个或多个纤程.操作系统随时可能夺走纤程所在线程的运行.当线程被

基于纤程(Fiber)实现C++异步编程(一):原理及示例

纤程(Fiber)和协程(coroutine)是差不多的概念,也叫做用户级线程或者轻线程之类的.Windows系统提供了一组API用户创建和使用纤程,本文中的库就是基于这组API实现的,所以无法跨平台使用,非Windows程序员可以闪人了,当然如果有兴趣可以继续看下去,找个第三方的协程库封装一下,也能实现相同的效果.关于纤程更详细的信息可以查阅MSDN. 纤程的概念中有两个关键点: 纤程拥有独立的栈空间和寄存器环境: 纤程在用户态实现调调度,也就是说完全由程序员控制: 下面的图表示了几个纤程相互

如何优雅的研究 RGSS3 番外(二) 显示文字信息的窗口中的纤程

Ruby 中的 Fiber 通常称为纤程,是一种非抢占式的多线程模型. 纤程不能在外部被终止,只能等待其主动让出执行权. RGSS3 在事件解释器 Game_Interpreter 与显示文字信息的窗口 Window_Message 中都用到了纤程. 以 Window_Message 为例. #-------------------------------------------------------------------------- # ● 更新画面 #-----------------

关于进程、线程、协程、管程、纤程、超线程的对比理解

1.进程 任务.作业(Job,Task,Schedule):在进程的概念出现之前,进程有着这样的称谓. 为了使多个程序能够并发(同一时刻只有一个在运行,但感觉起来像多个同时运行:并行(同一时刻真的多个在运行,不是感觉像多个))的执行,操作系统需要一个结构来抽象和表示这个程序的运行. 特性: 1.进程是操作系统对一个正在运行的程序的一种抽象结构. 2.进程是指在操作系统中能独立运行并作为资源分配的基本单位,由一组机器指令.数据和堆栈等组成的能独立运行的活动实体. 3.操作系统可以同时运行多个进程,

纤程调度器

INDY的纤程调度器调度纤程们到一个或多个线程中去.纤程储存工作项目 到一个工作序列中然后等待.当纤程的工作项目被完成后,调度器把纤程放到一个可以被调 度的纤程列表中. 操作系统时间调度器以一种智能的方式调度线程,但是由于在系统的所有任务间每个线程都 是普通和泛型的(generic),它们对线程的信息掌握十分有限.操作系统调度器只可以基于等待状态和线程的优先级来调度他们. context switche(线程的上下文切换) 是当一个线程被挂起,另一个被调度时.为了做这件事,操作系统必须先中 断处

纤程(FIBER)

Indy 10 还包含对纤程的支持.纤程是什么?简单来说,它也是 一个“线程”,但是它是由代码控制的,而不是由操作系统控制的.实际上,可以认为线程 是一个高级纤程.纤程和 Unix 用户线程(Unix user threads)很相似. 线程是操作系统用来分配时间的基本单元.一个线程包含它自己的栈(stack),特定的寄存器 (processor registers) ,以及一个线程上下文(thread context).线程们自动地被操作系统调度时间. 通常来说,纤程在一个设计良好的多线程应用

nodejs中的fiber(纤程)库详解

fiber/纤程 在操作系统中,除了进程和线程外,还有一种较少应用的纤程(fiber,也叫协程).纤程常常拿来跟线程做对比,对于操作系统而言,它们都是较轻量级的运行态.通常认为纤程比线程更为轻量,开销更小.不同之处在于,纤程是由线程或纤程创建的,纤程调度完全由用户代码控制,对系统内核而言,是一种非抢占性的调度方式,纤程实现了合作式的多任务:而线程和进程则受内核调度,依照优先级,实现了抢占式的多任务.另外,系统内核是不知道纤程的具体运行状态,纤程的使用其实是比较与操作系统无关. 在node中,单线