CN112230901A - 一种基于异步io模型的网络编程框架系统及方法 - Google Patents
一种基于异步io模型的网络编程框架系统及方法 Download PDFInfo
- Publication number
- CN112230901A CN112230901A CN202011180057.6A CN202011180057A CN112230901A CN 112230901 A CN112230901 A CN 112230901A CN 202011180057 A CN202011180057 A CN 202011180057A CN 112230901 A CN112230901 A CN 112230901A
- Authority
- CN
- China
- Prior art keywords
- node
- iobuffer
- thread
- queue
- processor
- Prior art date
- Legal status (The legal status is an assumption and is not a legal conclusion. Google has not performed a legal analysis and makes no representation as to the accuracy of the status listed.)
- Granted
Links
Images
Classifications
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/20—Software design
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F8/00—Arrangements for software engineering
- G06F8/30—Creation or generation of source code
- G06F8/31—Programming languages or programming paradigms
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/50—Allocation of resources, e.g. of the central processing unit [CPU]
- G06F9/5005—Allocation of resources, e.g. of the central processing unit [CPU] to service a request
- G06F9/5011—Allocation of resources, e.g. of the central processing unit [CPU] to service a request the resources being hardware resources other than CPUs, Servers and Terminals
- G06F9/5016—Allocation of resources, e.g. of the central processing unit [CPU] to service a request the resources being hardware resources other than CPUs, Servers and Terminals the resource being the memory
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/50—Allocation of resources, e.g. of the central processing unit [CPU]
- G06F9/5005—Allocation of resources, e.g. of the central processing unit [CPU] to service a request
- G06F9/5011—Allocation of resources, e.g. of the central processing unit [CPU] to service a request the resources being hardware resources other than CPUs, Servers and Terminals
- G06F9/5022—Mechanisms to release resources
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F9/00—Arrangements for program control, e.g. control units
- G06F9/06—Arrangements for program control, e.g. control units using stored programs, i.e. using an internal store of processing equipment to receive or retain programs
- G06F9/46—Multiprogramming arrangements
- G06F9/50—Allocation of resources, e.g. of the central processing unit [CPU]
- G06F9/5005—Allocation of resources, e.g. of the central processing unit [CPU] to service a request
- G06F9/5027—Allocation of resources, e.g. of the central processing unit [CPU] to service a request the resource being a machine, e.g. CPUs, Servers, Terminals
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F2209/00—Indexing scheme relating to G06F9/00
- G06F2209/50—Indexing scheme relating to G06F9/50
- G06F2209/5011—Pool
-
- G—PHYSICS
- G06—COMPUTING; CALCULATING OR COUNTING
- G06F—ELECTRIC DIGITAL DATA PROCESSING
- G06F2209/00—Indexing scheme relating to G06F9/00
- G06F2209/50—Indexing scheme relating to G06F9/50
- G06F2209/5018—Thread allocation
-
- Y—GENERAL TAGGING OF NEW TECHNOLOGICAL DEVELOPMENTS; GENERAL TAGGING OF CROSS-SECTIONAL TECHNOLOGIES SPANNING OVER SEVERAL SECTIONS OF THE IPC; TECHNICAL SUBJECTS COVERED BY FORMER USPC CROSS-REFERENCE ART COLLECTIONS [XRACs] AND DIGESTS
- Y02—TECHNOLOGIES OR APPLICATIONS FOR MITIGATION OR ADAPTATION AGAINST CLIMATE CHANGE
- Y02D—CLIMATE CHANGE MITIGATION TECHNOLOGIES IN INFORMATION AND COMMUNICATION TECHNOLOGIES [ICT], I.E. INFORMATION AND COMMUNICATION TECHNOLOGIES AIMING AT THE REDUCTION OF THEIR OWN ENERGY USE
- Y02D10/00—Energy efficient computing, e.g. low power processors, power management or thermal management
Abstract
本发明提供了一种基于异步IO模型的网络编程框架系统及方法,系统包括:通道层,用于接收待处理的原始数据;管道层,用于从所述通道层读取所述原始数据,并通过保存的多个处理器节点对所述原始数据进行操作;其中,所述多个处理器节点通过前向指针和后向指针首尾相连以形成责任链形式的链式处理模型,且每个处理器节点绑定有一个工作者线程,使得所述处理器节点的方法执行处于其绑定的工作者线程中。本实施例通过管道层和处理器节点上的处理器上下文的设计,为处理器节点提供单线程运行环境,既能给予每个处理器节点单线程的运行环境来减低编码的难度,又可以给予整体责任链模式串行化运行的可能来提升运行性能。
Description
技术领域
本发明涉及计算机技术领域,特别涉及一种基于异步IO模型的网络编程框架系统及方法。
背景技术
在网络编程方面,操作系统为上层应用提供了几种网络模型,分别是:阻塞IO模型,非阻塞IO模型,IO复用模型,信号驱动模型,异步IO模型。在语言层面的API方面,Java语言在JDK1.0时提供了Socket套接字接口,提供了对阻塞IO模型的支持。在JDK1.4版本的时候提供了对非阻塞IO模型的支持。在JDK1.7版本的时候提供了对异步IO模型的支持。
阻塞IO模型实现和应用起来较为简单,但是在并发量较高的时候,程序的整体性能不够高,开销也比较大。因为阻塞IO模型,针对每一个连接都需要消耗一个线程,而在长连接的情况下,对服务器的线程消耗会很大。
IO复用模型解决了阻塞IO模型在实现中对资源消耗大的问题。通过 IO复用技术,可以实现较少的线程支撑大量的接入链接。在高并发和大量接入的场景中,IO复用模型的性能较高。然而IO复用模型在实现上比较复杂,需要用户手动处理数据的读写操作。
发明内容
有鉴于此,本发明的目的在于提供一种基于异步IO模型的网络编程框架系统及方法,以解决上述问题。
本发明实施例提供了一种基于异步IO模型的网络编程框架系统,包括:
通道层,用于接收待处理的原始数据;
管道层,用于从所述通道层读取所述原始数据,并通过保存的多个处理器节点对所述原始数据进行操作;其中,所述多个处理器节点通过前向指针和后向指针首尾相连以形成责任链形式的链式处理模型,且每个处理器节点绑定有一个工作者线程,使得所述处理器节点的方法执行处于其绑定的工作者线程中。
本实施例提供的基于异步IO模型的网络编程框架系统,通过管道层和处理器节点上的处理器上下文的设计,为处理器节点提供单线程运行环境,既能给予每个处理器节点单线程的运行环境来减低编码的难度,又可以给予整体责任链模式串行化运行的可能来提升运行性能。
附图说明
为了更清楚地说明本发明实施方式的技术方案,下面将对实施方式中所需要使用的附图作简单地介绍,应当理解,以下附图仅示出了本发明的某些实施例,因此不应被看作是对范围的限定,对于本领域普通技术人员来讲,在不付出创造性劳动的前提下,还可以根据这些附图获得其他相关的附图。
图1是本发明第一实施例提供的基于异步IO模型的网络编程框架系统的模块示意图。
图2是绑定工作者线程和处理器节点的工作流程图。
图3是读取完成器的start方法的工作流程图。
图4是自适应分配内存的流程示意图。
图5是写出完成器的写出方法的流程示意图。
图6是写出完成器的队列写出数据的流程示意图。
图7是写出完成器的重置状态的流程示意图。
图8是stack结构的数据结构图。
图9是本发明第二实施例提供的基于异步IO模型的网络编程框架方法的流程示意图。
具体实施方式
为使本发明实施方式的目的、技术方案和优点更加清楚,下面将结合本发明实施方式中的附图,对本发明实施方式中的技术方案进行清楚、完整地描述,显然,所描述的实施方式是本发明一部分实施方式,而不是全部的实施方式。基于本发明中的实施方式,本领域普通技术人员在没有作出创造性劳动前提下所获得的所有其他实施方式,都属于本发明保护的范围。因此,以下对在附图中提供的本发明的实施方式的详细描述并非旨在限制要求保护的本发明的范围,而是仅仅表示本发明的选定实施方式。基于本发明中的实施方式,本领域普通技术人员在没有作出创造性劳动前提下所获得的所有其他实施方式,都属于本发明保护的范围。
请参阅图1,本发明第一实施例提供了一种基于异步IO模型的网络编程框架系统,包括:
通道层10,用于接收待处理的原始数据。
管道层20,用于从所述通道层10读取所述原始数据,并通过保存的多个处理器节点对所述原始数据进行操作;其中,所述多个处理器节点通过前向指针和后向指针首尾相连以形成责任链形式的链式处理模型,且每个处理器节点绑定有一个工作者线程,使得所述处理器节点的方法执行处于其绑定的工作者线程中。
在本实施例中,在异步IO(AIO)的API中,通道层10是对Socket 套接字的抽象,但是通道本身是双向的,可以同时进行读和写。对于读取而言,可能存在读取、解码、转化、逻辑处理。对于写出而言,则可能存在处理、转化、编码、写出形成的一个责任链形式的链式处理模型。
在本实施例中,管道层20是用来存放处理器节点的。对于外部API而言,只需要往管道层20中添加处理器节点,也就是WriteProcessor(写处理器)或者ReadProcessor(读处理器)。管道层20会生成包裹处理器节点的处理器上下文,也就是ProcessorContext。管道层20主要有如下两个作用:
1、为处理器节点生成处理器上下文,处理器上下文则为处理器节点提供了一个单线程运行环境。
2、提供默认的首尾两个处理器,方便于开发者集中关注在事务上。处理器上下文首先来说的是处理器上下文。
具体地,处理器上下文提供如下作用:
(1)、通过前向和后向指针,形成处理器上下文链条,支撑处理器节点形成责任链模式运作。
其中,每一次新增一个处理器上下文的时候,将自身的前向指向链条中最后一个,而最后一个上下文将自己的后向指向该新增的上下文。
(2)、每一个处理器上下文都会绑定一个工作者线程,确保处理器节点实例方法只会运行在该工作者线程上。
在本实施例中,在通道层10上读取到原始数据后,首先需要考虑的是将数据解码,并且解码得到一个业务上可以完整解释的一个业务包,然后业务包经过转换、计算、逻辑处理,完成业务应用并且可能需要生成应答数据。从单一职责的角度考虑,数据的读取、解码、转换、计算步骤,每一个步骤都应该使用单独的实现去完成对应的功能。而下游的输入就是上游的输出,也就是责任链模式。但只是简单的责任链模式还不够。Java的 API设计,在内核完成数据读取后,就会调用回调方法传入读取的数据进行处理,因此这个时候该线程是被占据的,在回调方法返回之前,该线程就无法用于其他地方。如果回调方法中有执行的比较慢的业务操作部分,比如涉及到IO操作。那么线程被占用的时间就会比较长。如果这种情况较多,将会导致线程整体消耗在数据读写上的时间变少,带来效率的下降。为解决这个问题,现有技术一般将读取、解码、转化这种消耗CPU的操作应该是直接利用初始AIO的线程池中来完成。而业务方法处理则可以视情况,放入到额外的线程池中进行处理,即将不同类型职责的线程分开。但是这样会带来一个难题,在责任链中的不同处理器节点,可能运行在不同的线程中,这将会导致并发处理上的困难,也就带来了实际编码上的困难。因此,对于责任链上的每一个单独的处理器节点,需要让这个节点运行在一个单独的线程中。这样,对于节点上处理器代码的开发,就简化为一个单线程的环境了。而如果责任链上的每一个点的运行线程一致的,则责任链的整体运行本身又是串行化的,可以取得最大的程序效能(减少了不必要的线程上下文切换开销)。如此,既能给予每个处理器节点单线程的运行环境来减低编码的难度,又可以给予整体责任链模式串行化运行的可能来提升运行性能。
在本实施例中,为了保证每个处理器节点单线程的运行环境,首先在新建上下文实例DefaultProcessorContext的时候,构造方法中需要两个必须入参:1)处理器实例;2)工作者对象。通过传入的这个工作者对象实例,将处理器上下文和这个实例绑定在一起。
如图2所示,在完成绑定后,所述处理器节点在执行业务时,首先判断调用方法的线程是否与处理器上下文绑定的工作者线程一致;若是,则直接执行对应的方法内容;若不是,则将这个调用包装为一个可运行对象,并将可运行对象投递到工作者线程的队列中,等待被处理。
在本实施例中,工作者线程的分配可通过工作者组接口来获取。该接口只有一个方法next,用来获取下一个工作者线程,具体到实现上,可以有不同的策略选择,主要有:
1、轮训选择。在初始化工作者组对象的时候,初始化一个数组的工作者对象,然后轮训选择其中一个作为下一个提供出去的工作者实例。
2、最小负载选择。将工作者绑定到节点上时进行标记。每次都选择绑定节点最少的工作者提供出去。
对于工作者而言,它是继承了Thread类,实现了JnetWorker接口。其本身内部有一个任务队列用于外部放入任务。工作者对象在初始化后就会调用start方法来启动线程。而其run方法的实现就是一个while循环,不断的从任务队列中取出任务进行执行。这个队列只有一个消费者,就是这个工作者本身,但是有多个生产者。因为这个工作者线程可以绑定不同的节点,而这些节点的上游节点也会绑定不同的工作者。因此会有多个工作者向这个工作者的队列中放入任务,所以这个队列是一个MPSC类型的队列。
从工作者的工作模式“死循环获取任务队列中的任务不断执行”,实现工作者有两种思路:
1、使用阻塞队列,工作者线程阻塞在任务的获取方法上,外部线程通过放入任务的方式,工作者线程从阻塞中解除,进而不断提取任务执行。
2、使用非阻塞队列,工作者线程在没有任务的情况下,通过 LockSupport类的功能暂停当前线程。当外部线程在队列中放入数据后,就通过LockSupport来唤醒工作者线程。
第一种方式实现简单,但是阻塞队列的性能会相对差一些。毕竟每一次投放任务都需要执行一次唤醒操作。
第二种方式实现较为复杂,但是性能较高。首先非阻塞队列的出队入队效率对比阻塞队列都是要高的。其次,放入任务的线程可以通过工作者状态的判断,如果工作者处于工作中,则需要执行唤醒,也就是说,如果任务稳定的投放,实际上大多数情况下都没有唤醒操作。对比方案一每次投放任务都需要执行唤醒,是节省了很多不需要的线程切换成本的。而对于工作者线程而言,只要队列有任务,就直接从中提取即可,无需让自身陷入暂停状态,也不用像方案一中调用阻塞队列可能导致的锁开销。
基于上述的考虑,本实施例工作者线程的实现上,优选地采用了第二种方案。
综上所述,本实施例提供的基于异步IO模型的网络编程框架系统,通过管道层和处理器节点上的处理器上下文的设计,为处理器节点提供单线程运行环境,既能给予每个处理器节点单线程的运行环境来减低编码的难度,又可以给予整体责任链模式串行化运行的可能来提升运行性能。
为便于对本发明的理解,下面对本发明的一些优选实施例做更进一步的描述。
在上述实施例的基础上,在本发明的一个优选实施例中,还包括:
自适应IoBuffer模块30,用于作为贯穿网络编程框架系统的数据承载容器以及连通网络编程框架系统的API与Java AIO中的API;其中,所述自适应IoBuffer模块30具有读取标记以及写入标记,并根据两个标记的切换实现读写的状态翻转;
分配器40,用于完成对IoBuffer的申请。
在现有框架中,Java中自带的API中使用的是ByteBuffer。这是一个抽象类,其具体的内部实现根据存储的空间是堆内内存则是 HeapByteBuffer,如果存储空间是直接内存则是DirectByteBuffer。ByteBuffer 主要的问题有两点:
1、API操作比较繁琐,需要了解position、limit、capacity属性。而且本身还具备读状态、写状态的区别。如果写入完成后需要读取还需要”翻转“操作,比较麻烦。
2、一旦申请完毕,容量是固定的。
第一点的话只是带了一些繁琐操作,第二点对编程的效率影响就比较大了。网络流量的不确定性,业务对象大小的不确定性,使得很难在一开始的时候就明确具体可能会使用的内存大小,自然也就无法申请到最合适的大小。常常会出现在使用中需要扩容的情况。而如果在业务逻辑操作中出现扩容的动作,对编程的连续性是有很大的影响的。
为了解决这个问题,本实施例创建了IoBuffer类。
具体地,IoBuffer只有writeIndex和readIndex,readIndex和writeIndex 两个标记位解决的是读写的状态翻转的繁琐问题,并没有读写状态的区分。读写操作都可以直接进行而无需进行状态切换。
IoBuffer在写入的过程中如果容量不足,会自动进行扩容动作。这扩容动作本身对上层是无感知的。其原理就是IoBuffer在每次写入的时候,都检查下当前剩余的空间是否满足写入的要求。如果不满足的话,则将底层的存储空间重新申请一个足够的大小,并且将当前存储空间的内容复制到新申请的存储空间中。之后“丢弃”(如果是开启了内存池,则此时将旧存储空间归还给内存池;否则就是直接丢弃,等待JVM进行GC)旧存储空间,将新申请的存储空间当成当前存储空间,继续在该空间上进行读写操作。
IoBuffer的底层可能是字节数组也可能是直接内存,这与ByteBuffer是相同的。而AIO中的API的承载媒介都是ByteBuffer。因此IoBuffer需要提供将与ByteBuffer的转换方法。更确切一些的说,需要提供一个方法,可以将自身内部存储的内容或者是可以写入的空间,以ByteBuffer的形式提供出去。具体的实现方式根据IoBuffer内部存储形式的不同也不同。如果内部存储是字节数组的话,可以直接依靠java.nio.ByteBuffer#wrap(byte[], int,int)方法将字节数组转化为ByteBuffer实例;如果内存存储是直接内存的话,在使用内存池的情况下,IoBuffer使用的内存区域是一个内存块中的某个部分,则可以通过java.nio.ByteBuffer#duplicate方法从DirectByteBuffer 中分出一个ByteBuffer的副本,并且设置其position和limit参数来将该 ByteBuffer对应到IoBuffer的具体内容上。
在本实施例中,分配器40主要用于完成对IoBuffer的申请。IoBuffer接口是为了屏蔽底层存储区域类型的复杂性。而分配器40则是为了屏蔽不同分配逻辑的复杂性。总体上来说,分配器40分为池化分配器和非池化分配器。池化分配器内部采用了内存池技术,对于内存的分配和回收更为高效。非池化分配器每次申请空间的时候都是直接新建。服务端程序一般应用池化分配器。客户端和测试的情况下,可以使用非池化分配器。
在上述实施例的基础上,在本发明的一个优选实施例中,还包括:
读取完成器50,用于在通道层10上启动监听以及在监听到通道层读取到数据后执行对应的处理逻辑;
其中,如图3所示,启动监听的过程(即star方法)具体为:
根据初始配置大小,从分配器申请IoBuffer实例;
将申请的IoBuffer实例设置到ReadEntry的对象属性,并使用IoBuffer 实例创建可写ByteBuffer,设置到ReadEntry属性;其中,ReadEntry类用于存储当前正在使用的IoBuffer和其生成出来的ByteBuffer;
使用所述ByteBuffer进行读取API调用,并将ReadEntry作为附件传入;
其中,如图4所示,执行对应的处理逻辑的过程(即complete方法) 具体为:
判断当前读取的数据的长度是否为-1;
若是,则将ReadEntry中的IoBuffer执行free调用来释放空间,并执行通道关闭方法;
若否,则判断当前读取的数据的长度是否大于0;
若大于,则IoBuffer中的写入标记增加本次读取的长度,并将IoBuffer 实例传递到管道层上,以触发管道层的读取方法;
若不大于,则将IoBuffer释放;
根据自适应算法判断本次需要的IoBuffer的容量大小,并调用分配器分配该大小的IoBuffer。
将所述IoBuffer设置到ReadEntry中,并使用所述IoBuffer创建可写 ByteBuffer实例,作为通道层读取的容器载体,将ReadEntry作为附件传入,执行通道读取。
优选地,根据自适应算法判断本次需要的IoBuffer的容量大小,并调用分配器分配该大小的IoBuffer,具体为:
在读取完成器初始化的时候对其赋予一个初始容量下标,根据所述初始容量下标从预设的数组中获取值,来确定IoBuffer的大小;其中,所述数组包括第一分段以及第二分段,所述第一分段中的数值为根据预算值递增;所述第二分段的值为根据预算倍速递增。
在本实施例中,实现自适应的IoBuffer大小调整,首先是有一个准备好的大小数组,如表1所示:
表1
IoBuffer的大小就是从该数组中找寻的值。读取完成器50初始化的时候被赋予一个初始容量下标,根据这个下标从数组中获取值,来确定 IoBuffer的大小。通过移动这个下标,也就是在对应的变化IoBuffer的大小。可以看到这个数组有2个分段,这个也是考虑到如果对方发送的数据比较小时,递增的幅度就小一些;如果发送的数据更大了,使用16作为递增单位则递增效率很低,此时就可以使用2倍递增。
在具体实现时,如果读取的字节数大于等于预期,则容量下标右移,也就是扩容一次;如果读取的字节数小于预期,则判断是否小于指定缩容大小(当前容量下标左移缩容步长得到的数组的值),第一次小于则设置一个标志位,第二次小于则执行缩容并复位标志位。在缩容上相对谨慎,避免一次接受的数据较少,而后续接受的数据较大又需要扩容导致增加读取次数。当完成容量下标的移动或者属性设置后,根据容量下标从大小数组中取出对应的大小,进行IoBuffer的申请。也就是通过控制容量下标的位置,来实现对IoBuffer大小的控制。
从读取完成器50的执行过程可以看出,进行过一次读取后,承载内容的IoBuffer都会被投递到管道层20中进行处理,并且重新申请一个IoBuffer 进行下一轮的读取。这种设计是从几个方面进行了考量。
首先,由于不确定所述管道层20后端的线程是否与读取完成器50使用了一样的线程,因此可能在调用管道的fireRead方法后,IoBuffer还没有消费完全方法就返回了。此时如果直接使用IoBuffer则可能会造成并发的问题。
第二,如果不直接使用该IoBuffer,也可以申请一个读取大小的 IoBuffer,将内容复制后,投递这新申请的IoBuffer。但是这么一来,在效率上会有所不足。首先是需要申请IoBuffer和复制内容;其次是如果经过自适应算法判断需要扩容,还是需要再次申请空间,也就是说这种方式可能会有1~2次的空间申请动作。对比目前的实现方式只有一次的空间申请动作,效率要低。
优选地,还包括:
写出完成器60,用于实现写处理器的写出方法,通过写出方法,实现对数据的写出;其中,写出方法在写出时,对需要写出的数据首先放入队列中,并配置队列消费标识位,使用竞争的方式获取到队列的消费权,才能执行消费队列的数据,执行写出操作而后再进行消费;消费包括:在通道上没有注册写出回调的时候,由调用写出方法的线程执行写出回调注册;在写出回调方法执行的时候,从队列中获取数据用于继续写出。
与读取完成器的接口定义十分相似,写出完成器60也定义了一个WriteEntry,用于存储IoBuffer和对应的ByteBuffer。道理也是相似的,因为在通道上调用的是Java的AIO的相关API,这些API只能通过 ByteBuffer来交互,而IoBuffer生成可以读取的ByteBuffer总是从头复制数据,因此需要保留初始的ByteBuffer,直到其写完为止。
写出完成器60最为重要的是要实现写处理器的写出方法,这是出站方向最终必然会调用到的方法。通过这个方法,实现了对数据的写出。
在设计写出完成器60之前,需要认识到一个问题:写出完成器中60 的部分方法是可能运行在多线程之下的。多线程来源是两点:
1、外部线程对写出方法的调用。
2、异步IO的API在写出数据完成之后调用到completed方法。
通常而言,一个通道上一次只能注册一个写出回调(写出回调也被写出完成器60实现了),但是写出方法却可能被连续调用。为此,本实施例的写出方法需要写出的数据首先放入队列中,而后再进行消费。
在处理过程中,有两个地方会产生消费:
1.在通道上没有注册写出回调的时候,由调用写出方法的线程执行写出回调注册。
2.在写出回调方法completed执行的时候,从队列中获取数据用于继续写出。
从队列中取出了数据随后就能注册写出回调,而写出回调在通道上单位时间内只能注册一个,这就意味着上述的两种情况不能并发,需要使用某种手段进行控制。为此,在本实施例中,写出完成器需要有一个队列消费标识位,使用竞争的方式获取到队列的消费权,才能执行消费队列的数据,执行写出操作。
基于这一点,本实施例的写出完成器60具有一个状态属性。该属性有两个值:1)空闲;2)工作中。
该属性初始为空闲,只有通过竞争,成功更新该属性为“工作中”的线程才能消费队列,并且在通道上注册写回调方法。
综合上述,本实施例的写出方法的实现流程如图5所示,其中:
本实施例中引入了一个实例参数:当前待写出字节总数,并声明为pendingWriteBytes。这个参数用于统计当前队列中字节总数,方便于在批量写出的时候获得队列中的字节总数。竞争成功,未必队列就存在数据。因此从查询写出完成器60状态到竞争更新成功,这个时间段内,其他线程可能已经完成了数据的写出工作。因此在竞争更新状态成功后,还需要进行队列内是否有数据的判断。如果存在数据,则执行“进行队列数据写出”工作。其流程如图6所示。
如果竞争更新状态成功,却发现队列中已经没有数据了。此时需要先释放队列的控制权限,也就是将写出完成器的状态设置为“空闲”。但是在状态设置为空闲后,并不能马上结束流程。因为在查询队列没有数据,到更新写出完成器这段时间内,可能其他线程已经又再次往队列中添加了数据,但是因为写出完成器的状态仍然是“工作中”,所以无法注册通道写出回调。因此这个线程在将写出完成器的状态设置为“空闲中”后,还需要进行队列是否为空检查,如果不为空,则需要再次发起竞争流程确保数据可以写出。因此,重置状态的流程如图7所示。
completed方法
上述提及的completed方法是写出完成器60的另外一个重要方法。这个方法是回调方法,也就是说当数据写出完毕的时候,内核就会回调这个方法,执行该方法的线程就是在在启动AIO服务器时候传入的线程,这个也是Java AIO的API提供的。
completed方法执行的时候,根据上面的分析可知,此时写出完成器60 的状态必然是工作中。对于这个方法而言,其实现内容是相对比较简单的,主要是几块构成:
1.如果上次传入的ByteBuffer尚未写出完毕,则继续注册写出回调,流程结束。
2.如果上次传入的ByteBuffer已经写出完毕,则释放WriteEntry中存储的IoBuffer。
3.步骤2执行完毕后,判断当前队列中是否存在数据。如果存在,则执行“进行队列写出”流程;如果不存在,则执行“重置状态流程”。
在上述实施例的基础上,在本发明的一个优选实施例中,还包括:
轻量级对象缓存池70,用于将对象实例缓存起来供后续分配使用来避免瞬时大量小对象反复生成和消亡造成分配和GC压力;其中:
在只有单线程的情况,轻量级对象缓存池构建一个Queue容器,对象回收时放入Queue,分配时从Queue取出,如果Queue中已经没有对象实例了,则创建一个新的实例;
在多线程的情况下,分配时从当前线程的Queue容器中取出一个对象或者创建一个对象实例分配;在回收时采用如下策略之一:
将对象回收至分配线程的Queue容器中;
将对象回收至本线程的Queue容器,当成本线程的对象使用;
策略三:将对象暂存于本线程,在后续合适时机归还至分配线程。
具体地,轻量级对象缓存池70实际上是一个相对独立的组件,也可以看成是工具类来直接使用。
轻量级对象缓存池70简单说就是将对象实例缓存起来供后续分配使用来避免瞬时大量小对象反复生成和消亡造成分配和GC压力。其中:
首先,考虑只有单线程的情况,此时简单的构建一个Queue容器,对象回收时放入Queue,分配时从Queue取出,如果Queue中已经没有对象实例了,则创建一个新的实例。这种方式非常容易就构建了一个单线程的对象池实现。额外需要考虑的细节就是为Queue设置一个大小的上限,避免池化的对象实例过多而导致消耗太多的内存。
接着,考虑多线程的情况,分配时与单线程情况相同,从当前线程的 Queue容器中取出一个对象或者创建一个对象实例分配即可。但回收时却遇到了麻烦,对象实例被回收时的线程和分配时的线程可能一致,那处理方式与单线程相同。如果不一致,此时存在不同的策略选择,大致来说有三种:
策略一:将对象回收至分配线程的Queue容器中。
策略二:将对象回收至本线程的Queue容器,当成本线程的对象使用
策略三:将对象暂存于本线程,在后续合适时机归还至分配线程。
其中,每一种策略均各自的优缺点。
对于策略一,由于存在多个线程并发的归还对象实例到借出线程,因此对于借出线程而言,其存储对象实例的Queue容器必须是MPSC类型,多个归还线程,一个借出线程。但是采用MPSC队列的形式,伴随着对象的借出和归还,队列内部的节点也是在不断的生成和消亡,这样就变相的降低了对象缓存池的效果。
对于策略二,如果线程之间呈现明显的借出和回收分工,则会导致对象缓存池失去作用。因为借出线程总是无法回收到对象,因此只能不断的新建对象实例,而回收线程因为很少分配对象,导致回收的对象超出上限被抛弃,从而无法有效的复用。
对于策略三,由于对象暂存于本线程,可以避开在回收时的并发竞争。并且因为在后续仍然会归还到借出线程,也避免了策略二可能导致的缓存失效情况。
基于上述考虑,本实施例优选的采用了策略三。
其中,在策略三的实现上。首先需要构建一个数据结构用于存储当前线程分配和回收的对象实例。为了避免在出列和入列上结构本身的开销,本实施例采用数组的方式来存储对象。内部通过一个指针来实现类似堆栈的压栈和弹栈功能。将这个结构定义为Stack,每一个Stack实例都单属于一个特定的线程,Stack通过线程变量的方式存储,避免全局竞争。每一个从Stack被申请的对象实例都属于这个特定的Stack实例,最终必然要回收到该Stack实例之中。
通过Stack结构,对于借出线程而言,就能支撑其对象的申请和回收。而要支撑在其他线程暂存借出对象,需要另外的数据结构。在应用运行的过程中,对于特定的一个线程而言,可能会有多个属于不同Stack实例的对象要被暂存。显然这些对象应该按照其从属的Stack实例区分开来,为此设计一个Map<Stack,Queue>的结构。使用Stack作为映射键来进行区分,为每一个Stack都生成一个Queue结构来存储暂存于本线程的从属于其他线程Stack的对象。当需要暂存其他线程的对象实例时,通过该对象关联的Stack,找到或者创建属于其的Queue结构,将对象实例放入这个 Queue结构中。这里的Queue结构,定义为WeakOrderQueue,这个名称是因为其只保证元素有序,但是并不保证及时的可见性,只保证一个最终可见。同样的,这个Map结构一样也是通过线程变量的方式来实现并发安全。通过Stack结构和Map结构,本实施例已经实现了当前线程回收和分配对象,其他线程暂存回收对象的效果。那么还差一个功能就是在合适的时机将回收对象归还至其从属的分配线程。对于这个功能,可以在Stack 的内部增加一个WeakOrderQueue列表,每当其他线程生成一个与Stack 关联的WeakOrderQueue,就将实例添加到Stack的WeakOrderQueue列表中。当Stack内部持有的对象数组没有元素供分配时,则遍历 WeakOrderQueue列表,将WeakOrderQueue中的数据转移到数组中。此时数组中就有了元素,就可以执行对象的分配了。
如图8所示,可以将Stack描述如下:
持有一个Element数组用于存储和分配回收对象。
持有WeakOrderQueue列表用于在element数组没有数据时从其他线程的WeakOrderQueue中转移数据到Element数组。
WeakOrderQueue是一个SPSC操作类型的结构,暂存线程放入对象,分配线程取出对象。在本实施例,WeakOrderQueue的设计既不是循环数组也不是SPSC队列。而是通过一个固定大小的片段数组来存储对象,每一个片段数组通过指针进行连接。内部保留Head、Tail两个指针。Head指针供Stack使用,标识当前正在读取的片段数组,Tail指针供WeakOrderQueue使用,标识最后一个数据可以写入的片段数组。
WeakOrderQueue为了完成SPSC操作(回收线程放入对象,原分配线程取出对象),其内部设计并非循环数组也不是SPSC队列,而是采用数组队列的方式。简单来说就是通过固定大小的片段数组存储对象,每一个片段数组通过Next指针链接。内部保留Head,Tail两个指针。Head指针供Stack使用,标识当前正在读取片段数组,Tail指针供WeakOrderQueue使用,标识最后一个数据可以写入的片段数组。
在上述实施例的基础上,在本发明的一个优选实施例中,还包括:
内存池80,用于提供内存的申请和释放;其中,在内存池中:
将一块连续的内存区域由2n个连续的单位空间构成;
采用完全二叉树管理2n个连续的单位空间,每一个单位空间对应一个叶子节点;节点的值代表该节点对应的内存区域内可以分配的连续空间长度,节点的初始值对应于其管理的区域大小;
如果两个节点的值都是初始值,则父节点的值也是初始值,否则父节点的值是2个子节点的值中的较大者;
在申请空间时,首先将申请大小规范化不小于其值的2n大小,称为规范大小;如果根节点的值小于规范大小,意味着无法分配;根节点的值大于等于规范大小,则存在可以分配的空间;
如果存在可以分配的空间,则从根节点开始向下寻找;
如果当前节点的值大于或者等于规范大小,则继续从子节点中寻找直到到达规范大小对应的维度,该维度上找到的节点的值与规范大小相等的节点可用于分配空间;
将分配节点的值更新为0,按照规则更新父节点的值,直到根节点为止。
在本实施例中,考虑到网络IO操作需要进行大量的数据读写,也就需要进行大量频繁的内存空间申请操作。如果这些操作都依赖JVM自身的话,那会对JVM产生很大的压力,而GC方面的影响也会影响到框架的整体稳定性,因为GC的发生出现峰值抖动等情况。
为了解决这个问题,就需要提供一个内存池供框架直接使用从而绕开 JVM的申请和分配,这样一来,也就不存在GC的问题了。
在本实施例中,内存池80的基本作用就是提供内存的申请和释放。而为了在这个过程中减少GC,申请和释放的空间必然是其本身持有,不是在申请和释放的过程中再次向JVM申请。因此内存池本身一定是持有一大块可以用于分配的内存。对于socket通道而言,其数据读写的载体是byte[] 对象。那么内存池的设计就可以简化为如何在一个byte[]上高效的申请和释放。
对于申请而言,其难度在于两个方面:
如何判断申请的长度在对应的byte[]存在足够的空闲空间。
如何寻找申请长度在byte[]上对应的空闲空间。
对于问题二,在初始情况下,在一个"空白"的byte[]上申请空间是最容易的,因为此时只需要判断数组的长度和申请的长度就能明确是否可以申请,并且可以在任意足够长度的下标处选择其作为起点进行空间分配。但是进行第二次分配的时候就很麻烦了。可以使用一个变量用来记录当前 byte[]剩余可分配的空间,可以可以快速判断是否有足够的空间进行分配。不过在具体分配的时候就需要避开之前已经分配的空间,而如果去寻找之前分配的空间就成了首要问题。
为便于理解,下面将一以实际的应用场景来分析这个问题。假定一开始有16个字节的内存区域可以分配,如表2:
表2
如果要申请一个8字节的空间,申请后的情况可能如表3所示,也可能如表4所示(颜色浅的数字表示被申请的空间):
表3
表4
显然,对于表4的情况,如果还要申请一块8字节的空间,虽然整体而言有可以分配的容量,却也是无法申请到空间了。因此,在分配空间的时候应该优先采用第一种分配模式的模式,尽可能的靠前分配,减少碎片空间的产生。
再考虑下分配时寻找空间的问题,显然需要有一种方式来标记已经被使用的空间,再分配的时候避免分配空间的重叠(重叠的空间会带来数据的紊乱)。
最最简单的思路,使用位图来标记每一个可以分配的最小单位。假定可以分配的最小单位是1k的字节。16个1k的byte[]就可以使用byte[2] 来表示是否使用中。在检查分配的时候,从头开始遍历位图,当发现空闲空间时开始标记计算,如果在遇到不可用区域前满足了申请长度,则表示可以申请成功。
这种查找方法无疑效率是很低的。并且在空间碎片化程度很高的时候,可能会出现空闲空间的总容量足够,但是却无法分配足够长度的连续空间的问题。因此无论是通过记录空闲空间总量来判断是否可以分配以及线性查找方式确认分配空间起始标识的做法,两者都是不可取的。
废弃掉这个解决方案,再回头思考判断是否可以申请的问题,实际上应该使用当前内存池最大连续长度来判断是否可以申请。如果申请的长度连最大空闲连续长度都无法满足,必然是无法申请的。
而对于查找而言,现有的思路是线性查找,其方法如表5所示:
表5
为了提升效率,可以想到的是将线性查找O(n)换成二分查找 O(logn)。观察上面的示意图,判断分配单位是否空闲只有一个维度;如果增加更多的维度,每一层维度都是下层维度的聚合,那么查询上层维度显然就更快了,因为上层维度的长度更短。从这个角度出发,可以将查找的思路抽象为:
表6
显然,要申请一个空间为4的区域,从维度2开始寻找,只需要寻找2 个空闲的区域即可;而从维度1开始寻找,则需要寻找连续的4个区域,寻找的时间成本就变为2倍。推而广之,可以将维度继续升高,最终会变成如表7的情况:
表7
这种表达方式就变成了一颗完全二叉树。当需要申请空间时,首先要找到合适的维度。为此,就需要对申请的大小进行规范化,不能再申请任意的大小(只需要最终给出的大小大于等于申请的大小,就是满足客户方要求的),申请的大小必须是某一个维度上的单位值(比如上表的1,2,4, 8,16)。申请的时候从顶层维度查找,如果申请大小不相符则向下下级维度继续查询。查询到符合的维度后,在该维度上遍历,确认可以使用的区域。
表8
比如维度3上第一个区域被使用了,意味着其管理的内存空间的1,2, 3,4处于被使用状态。维度上的每一个区域都标记了当前管理区域可以分配的最大连续空间。仍然以表8为例,如果维度3的第一个区域被使用了,其可分配空间下降为0。此时其父节点,也就是维度4的第一个区域,显然无法继续分配大小为8的空间了。其能分配的空间为8-4=4。
因此,当一个维度上的某个区域被分配后,应该向上更新父节点区域的可分配长度大小。父节点的可分配大小取决于2个子节点。当两个子节点都没有分配的情况下,父节点的可分配大小是2个子节点之和,或者说此时是初始值。如果两个子节点有一个分配后,父节点的可分配大小应该是2 个子节点中较大的那个值。
这个更新大小的动作应该不断向上执行,到达根节点。比如申请1k的空间,申请成功后,内存池应该如表9所示:
表9
如果此时要再申请2k的大小,内存池变化为如表10所示:
表10
通过这种方式,即同时解决了确认是否可分配以及如何分配两个问题,
根据上面的过程,可以将对一块连续内存区域的分配算法描述如下:
将一块连续的内存区域由2n个连续的单位空间构成;
采用完全二叉树管理2n个连续的单位空间,每一个单位空间对应一个叶子节点;节点的值代表该节点对应的内存区域内可以分配的连续空间长度,节点的初始值对应于其管理的区域大小;
如果两个节点的值都是初始值,则父节点的值也是初始值,否则父节点的值是2个子节点的值中的较大者;
在申请空间时,首先将申请大小规范化不小于其值的2n大小,称为规范大小;如果根节点的值小于规范大小,意味着无法分配;根节点的值大于等于规范大小,则存在可以分配的空间;
如果存在可以分配的空间,则从根节点开始向下寻找;
如果当前节点的值大于或者等于规范大小,则继续从子节点中寻找直到到达规范大小对应的维度,该维度上找到的节点的值与规范大小相等的节点可用于分配空间;
将分配节点的值更新为0,按照规则更新父节点的值,直到根节点为止。
在具体的实现时,每个节点存储的值不是该节点管理的空间大小,而是该节点代表的子树能分配的最小深度(深度越小,意味着越靠近根节点)。节点管理内存大小是2h-n个内存单位,h是管理内存空间的二叉树的最大深度,n是该节点的深度。也就是说,节点的深度决定了其管理的内存大小。如果一个节点的值大于h,则意味着该节点没有空间可以再分配内存了。虽然换了一种表述方式,但是其本质是相同的,因此可以将其内存80 的分配流程通过如下流程表述:
对于一块连续的内存区域由2n个连续的单位空间构成:
采用完全二叉树管理2n个连续的单位空间,每一个单位空间对应一个叶子节点。
完全二叉树的高度为h,每一个节点管理的最大单位空间数量为2h-n,n为当前节点的深度。
节点的值代表该节点当前最大可以分配的空间所在的深度,也就是可以分配的最小深度。节点的初始值与节点的深度相同。
如果两个节点的值都是初始值,则父节点的值也是初始值,否则父节点的值是2个子节点的值中的较小者。
申请空间时,首先将申请大小规范化不小于其值的2d*单位空间(比如1k)大小,获取d值,称为规范大小。规范大小实际上就是所要分配合适空间大小的节点对应的深度。
如果根节点的值小于规范大小,意味着可以分配。根节点的值大于等于规范大小,则意味着对应的深度已经分配完毕了,本次申请无法成功。如果存在可以分配的空间,则从根节点开始向下寻找直到到达对应的深度。寻找过程中,如果左子节点的值小于规范大小则尝试右子节点。
到达对应深度的节点即为可以分配的节点。将分配节点的值更新为 h+1,按照规则更新父节点的值,直到根节点为止。
请参阅图9,本发明第二实施例还提供了一种基于异步IO模型的网络编程方法,包括:
S201,接收待处理的原始数据;
S202,从所述通道层读取所述原始数据,并通过保存的多个处理器节点对所述原始数据进行操作;其中,所述多个处理器节点通过前向指针和后向指针首尾相连以形成责任链形式的链式处理模型,且每个处理器节点绑定有一个工作者线程,使得所述处理器节点的方法执行处于其绑定的工作者线程中。
以上所述仅为本发明的优选实施例而已,并不用于限制本发明,对于本领域的技术人员来说,本发明可以有各种更改和变化。凡在本发明的精神和原则之内,所作的任何修改、等同替换、改进等,均应包含在本发明的保护范围之内。
Claims (10)
1.一种基于异步IO模型的网络编程框架系统,其特征在于,包括:
通道层,用于接收待处理的原始数据;
管道层,用于从所述通道层读取所述原始数据,并通过保存的多个处理器节点对所述原始数据进行操作;其中,所述多个处理器节点通过前向指针和后向指针首尾相连以形成责任链形式的链式处理模型,且每个处理器节点绑定有一个工作者线程,使得所述处理器节点的方法执行处于其绑定的工作者线程中。
2.根据权利要求1所述的基于异步IO模型的网络编程框架系统,其特征在于,
所述通道层提供的通道为读写双向;则对管道层:
在读取时,将其视为源头是对通道层的原始数据的读取,尽头是处理器节点的一个责任链的链式处理模型;
在写出时,将其视为存在处理、转化、编码、写出形成的一个责任链形式的链式处理模型。
3.根据权利要求1所述的基于异步IO模型的网络编程框架系统,其特征在于,每个处理器节点均处于一个处理器上下文的包裹之中,则对于每个处理器节点:
判断调用方法的线程是否与处理器上下文绑定的工作者线程;
若是,则直接执行对应的方法内容;
若不是,则将这个调用包装为一个可运行对象,并将所述可运行对象投递到工作者线程的队列中,等待被处理。
4.根据权利要求1所述的基于异步IO模型的网络编程框架系统,其特征在于,还包括:
自适应IoBuffer模块,用于作为贯穿网络编程框架系统的数据承载容器以及连通网络编程框架系统的API与Java AIO中的API;其中,所述自适应IoBuffer模块具有读取标记以及写入标记,并根据两个标记的切换实现读写的状态翻转;
分配器,用于完成对IoBuffer的申请。
5.根据权利要求4所述的基于异步IO模型的网络编程框架系统,其特征在于,还包括:
读取完成器,用于在通道层上启动监听以及在监听到通道层读取到数据后执行对应的处理逻辑;
其中,启动监听的过程具体为:
根据初始配置大小,从分配器申请IoBuffer实例;
将申请的IoBuffer实例设置到ReadEntry的对象属性,并使用IoBuffer实例创建可写ByteBuffer,设置到ReadEntry属性;其中,ReadEntry类用于存储当前正在使用的IoBuffer和其生成出来的ByteBuffer;
使用所述ByteBuffer进行读取API调用,并将ReadEntry作为附件传入;
其中,执行对应的处理逻辑的过程具体为:
判断当前读取的数据的长度是否为-1;
若是,则将ReadEntry中的IoBuffer执行free调用来释放空间,并执行通道关闭方法;
若否,则判断当前读取的数据的长度是否大于0;
若大于,则IoBuffer中的写入标记增加本次读取的长度,并将IoBuffer实例传递到管道层上,以触发管道层的读取方法;
若不大于,则将IoBuffer释放;
根据自适应算法判断本次需要的IoBuffer的容量大小,并调用分配器分配该大小的IoBuffer;
将所述IoBuffer设置到ReadEntry中,并使用所述IoBuffer创建可写ByteBuffer实例,作为通道层读取的容器载体,将ReadEntry作为附件传入,执行通道读取。
6.根据权利要求5所述的基于异步IO模型的网络编程框架系统,其特征在于,根据自适应算法判断本次需要的IoBuffer的容量大小,并调用分配器分配该大小的IoBuffer,具体为:
在读取完成器初始化的时候对其赋予一个初始容量下标,根据所述初始容量下标从预设的数组中获取值,来确定IoBuffer的大小;其中,所述数组包括第一分段以及第二分段,所述第一分段中的数值为根据预算值递增;所述第二分段的值为根据预算倍速递增。
7.根据权利要求1所述的基于异步IO模型的网络编程框架系统,其特征在于,还包括:
写出完成器,用于实现写处理器的写出方法,通过写出方法,实现对数据的写出;其中,写出方法在写出时,对需要写出的数据首先放入队列中,并配置队列消费标识位,使用竞争的方式获取到队列的消费权,并根据消费权执行消费队列的数据,执行写出操作而后再进行消费;消费包括:在通道上没有注册写出回调的时候,由调用写出方法的线程执行写出回调注册;在写出回调方法执行的时候,从队列中获取数据用于继续写出。
8.根据权利要求1所述的基于异步IO模型的网络编程框架系统,其特征在于,还包括:
轻量级对象缓存池,用于将对象实例缓存起来供后续分配使用来避免瞬时大量小对象反复生成和消亡造成分配和GC压力;其中:
在只有单线程的情况,轻量级对象缓存池构建一个Queue容器,对象回收时放入Queue,分配时从Queue取出,如果Queue中已经没有对象实例了,则创建一个新的实例;
在多线程的情况下,分配时从当前线程的Queue容器中取出一个对象或者创建一个对象实例分配;在回收时采用如下策略之一:
将对象回收至分配线程的Queue容器中;
将对象回收至本线程的Queue容器,当成本线程的对象使用;
策略三:将对象暂存于本线程,在后续合适时机归还至分配线程。
9.根据权利要求1所述的基于异步IO模型的网络编程框架系统,其特征在于,还包括:
内存池,用于提供内存的申请和释放;其中,在内存池中:
将一块连续的内存区域由2n个连续的单位空间构成;
采用完全二叉树管理2n个连续的单位空间,每一个单位空间对应一个叶子节点;节点的值代表该节点对应的内存区域内可以分配的连续空间长度,节点的初始值对应于其管理的区域大小;
如果两个节点的值都是初始值,则父节点的值也是初始值,否则父节点的值是2个子节点的值中的较大者;
在申请空间时,首先将申请大小规范化不小于其值的2n大小,称为规范大小;如果根节点的值小于规范大小,意味着无法分配;根节点的值大于等于规范大小,则存在可以分配的空间;
如果存在可以分配的空间,则从根节点开始向下寻找;
如果当前节点的值大于或者等于规范大小,则继续从子节点中寻找直到到达规范大小对应的维度,该维度上找到的节点的值与规范大小相等的节点可用于分配空间;
将分配节点的值更新为0,按照规则更新父节点的值,直到根节点为止。
10.一种基于异步IO模型的网络编程方法,其特征在于,包括:
接收待处理的原始数据;
从所述通道层读取所述原始数据,并通过保存的多个处理器节点对所述原始数据进行操作;其中,所述多个处理器节点通过前向指针和后向指针首尾相连以形成责任链形式的链式处理模型,且每个处理器节点绑定有一个工作者线程,使得所述处理器节点的方法执行处于其绑定的工作者线程中。
Priority Applications (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN202011180057.6A CN112230901B (zh) | 2020-10-29 | 2020-10-29 | 一种基于异步io模型的网络编程框架系统及方法 |
Applications Claiming Priority (1)
Application Number | Priority Date | Filing Date | Title |
---|---|---|---|
CN202011180057.6A CN112230901B (zh) | 2020-10-29 | 2020-10-29 | 一种基于异步io模型的网络编程框架系统及方法 |
Publications (2)
Publication Number | Publication Date |
---|---|
CN112230901A true CN112230901A (zh) | 2021-01-15 |
CN112230901B CN112230901B (zh) | 2023-06-20 |
Family
ID=74109809
Family Applications (1)
Application Number | Title | Priority Date | Filing Date |
---|---|---|---|
CN202011180057.6A Active CN112230901B (zh) | 2020-10-29 | 2020-10-29 | 一种基于异步io模型的网络编程框架系统及方法 |
Country Status (1)
Country | Link |
---|---|
CN (1) | CN112230901B (zh) |
Cited By (1)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN114442926A (zh) * | 2021-12-21 | 2022-05-06 | 天津光电通信技术有限公司 | 基于PCIe数据采集卡的光信号数据分析与处理方法 |
Citations (4)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN1542607A (zh) * | 2003-04-21 | 2004-11-03 | �Ҵ���˾ | 同时多线程处理器及提高其性能的方法 |
CN102906706A (zh) * | 2010-05-24 | 2013-01-30 | 索尼电脑娱乐公司 | 信息处理装置及信息处理方法 |
US20160328488A1 (en) * | 2015-05-08 | 2016-11-10 | Seth Lytle | Structure linked native query database management system and methods |
CN107980118A (zh) * | 2015-06-10 | 2018-05-01 | 无比视视觉技术有限公司 | 使用多线程处理的多核处理器设备 |
-
2020
- 2020-10-29 CN CN202011180057.6A patent/CN112230901B/zh active Active
Patent Citations (4)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN1542607A (zh) * | 2003-04-21 | 2004-11-03 | �Ҵ���˾ | 同时多线程处理器及提高其性能的方法 |
CN102906706A (zh) * | 2010-05-24 | 2013-01-30 | 索尼电脑娱乐公司 | 信息处理装置及信息处理方法 |
US20160328488A1 (en) * | 2015-05-08 | 2016-11-10 | Seth Lytle | Structure linked native query database management system and methods |
CN107980118A (zh) * | 2015-06-10 | 2018-05-01 | 无比视视觉技术有限公司 | 使用多线程处理的多核处理器设备 |
Cited By (1)
Publication number | Priority date | Publication date | Assignee | Title |
---|---|---|---|---|
CN114442926A (zh) * | 2021-12-21 | 2022-05-06 | 天津光电通信技术有限公司 | 基于PCIe数据采集卡的光信号数据分析与处理方法 |
Also Published As
Publication number | Publication date |
---|---|
CN112230901B (zh) | 2023-06-20 |
Similar Documents
Publication | Publication Date | Title |
---|---|---|
US9798595B2 (en) | Transparent user mode scheduling on traditional threading systems | |
US6052739A (en) | Method and apparatus for object-oriented interrupt system | |
US7373640B1 (en) | Technique for dynamically restricting thread concurrency without rewriting thread code | |
US6732138B1 (en) | Method and system for accessing system resources of a data processing system utilizing a kernel-only thread within a user process | |
JP5367816B2 (ja) | オペレーションの保護モードスケジューリング | |
TWI539280B (zh) | 用於分析未經特定設計以提供記憶體分配資訊之應用程式及擷取記憶體分配資訊的方法、及其電腦系統和電腦可讀儲存媒體 | |
US11620215B2 (en) | Multi-threaded pause-less replicating garbage collection | |
US9176769B2 (en) | Partitioned array objects in a distributed runtime | |
JP2005534116A (ja) | 複数の消費者をもつコンピュータシステムで資源を動的に割当てて管理する方法 | |
US8032884B2 (en) | Thread hand off | |
CN102985910A (zh) | 对无用存储单元收集的gpu支持 | |
WO2012083188A1 (en) | Distributed computing architecture | |
WO2021022964A1 (zh) | 一种基于多核系统的任务处理方法、装置及计算机可读存储介质 | |
US8954969B2 (en) | File system object node management | |
US9535678B2 (en) | Providing distributed array containers for programming objects | |
CN112230901A (zh) | 一种基于异步io模型的网络编程框架系统及方法 | |
CN112346835B (zh) | 一种基于协程的调度处理方法及系统 | |
CN111125070A (zh) | 一种数据交换方法及平台 | |
US20100299672A1 (en) | Memory management device, computer system, and memory management method | |
JP2022079764A (ja) | 同期制御システムおよび同期制御方法 | |
WO2023116910A1 (zh) | 一种计算资源和缓存资源调度方法、装置及系统 | |
US9384063B2 (en) | Eliding synchronization in a concurrent data structure | |
Dissaux et al. | Cheddar Architecture Description Language | |
CN116662394A (zh) | 一种基于Java的响应优先式缓存加载方法 | |
CN115576697A (zh) | 一种基于任务调度的嵌入式动态内存分配系统及方法 |
Legal Events
Date | Code | Title | Description |
---|---|---|---|
PB01 | Publication | ||
PB01 | Publication | ||
SE01 | Entry into force of request for substantive examination | ||
SE01 | Entry into force of request for substantive examination | ||
CB02 | Change of applicant information | ||
CB02 | Change of applicant information |
Address after: 361000 one of 504, No. 18, guanri Road, phase II, software park, Xiamen, Fujian Applicant after: XIAMEN YILIANZHONG YIHUI TECHNOLOGY CO.,LTD. Address before: Room 504, No.18, guanri Road, phase II, software park, Xiamen City, Fujian Province, 361000 Applicant before: XIAMEN YILIANZHONG YIHUI TECHNOLOGY CO.,LTD. |
|
GR01 | Patent grant | ||
GR01 | Patent grant |