发明内容
本公开的一个目的在于提供一种能够对动态类型语言进行优化的方案。
根据本公开的第一个方面,提出了一种针对动态类型语言的优化方法,包括:对于基于动态类型语言编写的程序代码中的至少一个第一对象,从第一对象所在的原型链中提取第一对象的属性;以及将属性作为第一对象的一部分进行存储。
可选地,优化方法还包括:在对第一对象的属性执行读和/或写操作的情况下,仅对该第一对象进行加锁处理。
可选地,将属性作为第一对象的一部分进行存储的步骤包括:将属性存储在为第一对象分配的存储空间。
可选地,第一对象是在程序代码运行过程中能够被至少两个线程调用的对象。
可选地,设置为在运行过程中使用原型链的读写方式调用第一对象的情况下,返回错误。
可选地,优化方法还包括:对第一对象具有的属性进行换名;并且为第一对象具有的属性生成同名的getter方法和setter方法。
可选地,优化方法还包括:在getter方法和/或setter方法内插入封闭性违例检测,用于检测第一对象是否引用了第二对象,其中,第二对象为程序代码的运行过程中仅能够被单个线程调用的对象。
可选地,第一对象被设置为仅能够引用其它第一对象,第二对象被设置为能够引用第一对象以及被第二对象对应的线程调用的其它第二对象。
可选地,第一对象存储在第一内存堆中,第一内存堆中的对象能够被多个线程调用,并且第二对象存储在调用其的线程对应的第二内存堆中,第二内存堆中的对象仅能够被其对应的线程调用。
根据本公开的第二个方面,还提供了一种针对动态类型语言的优化装置,包括:属性提取模块,用于对于基于动态类型语言编写的程序代码中的至少一个第一对象,从第一对象所在的原型链中提取第一对象的属性;以及属性保存模块,用于将属性作为第一对象的一部分进行存储。
可选地,优化装置还包括:加锁模块,用于在对第一对象的属性执行读和/或写操作的情况下,仅对该第一对象进行加锁处理。
可选地,属性保存模块将属性存储在为第一对象分配的存储空间。
可选地,第一对象是在程序代码运行过程中能够被至少两个线程调用的对象。
可选地,设置为在运行过程中使用原型链的读写方式调用第一对象的情况下,返回错误。
可选地,优化装置还包括:换名模块,用于对第一对象具有的属性进行换名;和/或生成模块,用于为第一对象具有的属性生成同名的getter方法和setter方法。
可选地,优化装置还包括:插入模块,用于在getter方法和/或setter方法内插入封闭性违例检测,封闭性违例检测用于检测第一对象是否引用了第二对象,其中,第二对象为程序代码的运行过程中仅能够被单个线程调用的对象。
可选地,第一对象被设置为仅能够引用其它第一对象,第二对象被设置为能够引用第一对象以及被第二对象对应的线程调用的其它第二对象。
可选地,第一对象存储在第一内存堆中,第一内存堆中的对象能够被多个线程调用,并且第二对象存储在调用其的线程对应的第二内存堆中,第二内存堆中的对象仅能够被其对应的线程调用。
根据本公开的第三个方面,还提供了一种计算设备,包括:处理器;以及存储器,其上存储有可执行代码,当可执行代码被处理器执行时,使处理器执行如本公开第一个方面或第二个方面述及的方法。
根据本公开的第四个方面,还提供了一种非暂时性机器可读存储介质,其上存储有可执行代码,当可执行代码被电子设备的处理器执行时,使处理器执行如本公开第一个方面或第二个方面述及的方法。
本公开通过从原型链中提取对象的属性,并将其存储在对象本身上,可以使得对象被调用时,无需锁定整个原型链。
具体实施方式
下面将参照附图更详细地描述本公开的优选实施方式。虽然附图中显示了本公开的优选实施方式,然而应该理解,可以以各种形式实现本公开而不应被这里阐述的实施方式所限制。相反,提供这些实施方式是为了使本公开更加透彻和完整,并且能够将本公开的范围完整地传达给本领域的技术人员。
【术语解释】
动态类型语言:Dynamic-Typed Language,是指程序在运行时可以改变其结构,新的函数可以被引进,已有的函数可以被删除。本公开述及的动态类型语言特别是指支持以原型链的方式实现继承的动态类型语言,例如JS语言。
JS:JavaScript语言的简称,是一种动态类型语言。
原型链:Prototype Chain,动态类型语言中实现继承的主要方式。基本思路是,利用原型让一个引用类型继承另一个引用类型的属性和方法。每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针(constructor),而实例对象都包含一个指向原型对象的内部指针(__proto__)。如果让原型对象等于另一个类型的实例,此时的原型对象将包含一个指向另一个原型的指针(__proto__),另一个原型也包含着一个指向另一个构造函数的指针(constructor)。假如另一个原型又是另一个类型的实例……这就构成了实例与原型的链条,即原型链。
Sobject:Shared And Static Object的简称,本公开提出的一种受限制的共享对象,可以是本文述及的第一对象。
Web Worker:W3C发布的给Web应用开发提供后台并行地执行JavaScript代码能力的一种机制,定义了一套API标准。本公开述及的Worker是Web Worker的简称。
Heap:内存堆,以动态类型语言为JS为例,可以是由JS虚拟机管理的存放JS运行时对象的内存区域。
AST,抽象语法树(Abstract Syntax Tree)的简称。
【方案概述】
图1是示出了根据本公开一实施例的针对动态类型语言的优化方法的示意性流程图。其中,图1所示的方法可以由对基于动态类型语言编写的程序代码进行处理的设备(如虚拟机)执行,例如在动态类型语言为JS的情况下,可以由JS虚拟机(例如V8虚拟机)执行。
参见图1,在步骤S110,对于基于动态类型语言编写的程序代码中的至少一个第一对象,从第一对象所在的原型链中提取第一对象的属性。
理论上,第一对象可以是程序代码中的任何对象。以JS语言为例,JS中的所有事物都是对象,对象是拥有属性和方法的数据,因此第一对象可以是JS代码中的任何对象。
根据动态类型语言的原型链特性可知,一个对象的属性的实际存储位置可能并不在对象本身,而是可能存储在对象的原型链上的任何位置。因此,针对第一对象,可以从第一对象所在的原型链中提取第一对象的属性,如可以将存储在原型链中的第一对象的所有属性提取出来。
可以在对程序代码进行解析的过程中执行这一属性提取过程,如可以在利用编译器对程序代码进行语法分析,以生成抽象语法树(AST)的过程中,分析第一对象的原型链,并从第一对象所在的原型链中提取第一对象的属性。
在步骤S120,将提取的属性作为第一对象的一部分进行存储。
在第一对象的属性包括在第一对象上存储的属性和在原型链上其它地方存储的属性的情况下,可以将从原型链提取的属性与第一对象上本身具有的属性一同存储,如可以是物理上存储在一起。例如,可以将提取的属性存储在第一对象所对应的存储空间,此处述及的存储空间可以是为第一对象分配的内存空间,即可以将提取的属性存储在为第一对象分配的存储空间。
在对第一对象的属性执行读和/或写操作的情况下,由于第一对象的所有属性均存储在第一对象本身中,因此可以仅对第一对象进行加锁处理,而不需要再锁住第一对象的整个原型链。
综上,本公开通过从原型链中提取对象的属性,并将其存储在对象本身上,可以使得对象被调用时,无需锁定整个原型链。基于这种“去原型链化”的构思,本公开进一步提出,可以优选地对程序代码运行过程中能够被多线程(Multithread)调用的对象执行去原型链化的操作。
也就是说,上文述及的第一对象可以是程序代码运行过程中能够被至少两个线程调用的对象(即共享对象)。可以将共享对象的属性从原型链中提取出来,并保存在共享对象本身上。如此,在线程调用某一共享对象的情况下,可以仅对该共享对象进行加锁处理,无需再确定该共享对象的原型链,更不用锁住原型链。由此,在线程调用某一共享对象的情况下,无需所有线程都停止工作,即其它线程仍可以正常调用其它共享对象或者非共享对象(普通对象,即下文述及的第二对象),从而可以实现对象的同步共享。
下面就本公开涉及的各方面做进一步说明。
【第一对象/第二对象】
对于支持原型链的动态类型语言(例如Javascript),如背景技术部分所述,原型链是多线程的最大障碍,使得用户无法使用Monitor之类的锁同步对象。同步一个对象时,所有对象的读写必须停止(Stop The World)。所以普通对象不能作为共享对象在多线程之间共享,因为没有合适用户使用的同步机制,使得用户很难写出线程安全的程序。
有鉴于此,本公开设计了一种与普通对象(为了便于区分,可以称为“第二对象”)不同的对象(共享对象,可以是上文述及的第一对象)。
第一对象可以是程序代码运行过程中能够被多个(至少两个)线程使用的对象,即共享对象(Global Object)。与第一对象不同,第二对象为程序代码的运行过程中仅能够被单个线程调用的对象,第二对象可以视为线程私有对象(Thread Local Object),只能在创建该对象的线程中使用。
与第二对象不同,本公开主要在“去原型链化”、“内存模型”以及“封闭性”这三个方面对第一对象做了限定,以使得第一对象可以作为多线程共享对象。因此,本公开述及的第一对象可以视为一种受限制的共享对象(Sobject)。下面就这三方面分别进行说明。
【去原型链化】
正如上文结合图1的描述,可以将Sobject的属性从其所在的原型链中提取到Sobject本身的存储空间存储,以保证对象的数据属性一定存储在Sobject上。某个线程在读写第一对象时,只锁这个Sobject就能够保证线程安全,而不需要再锁住整个原型链。因此,线程在读写Sobject时,不必像现有的对象读写同步那样,必须使得所有线程停止。
并且,由于Sobject的所有属性数据均存储在其本身中,因此对于Sobject,可以禁止原型链的读写,即可以设置为在运行过程中使用原型链的读写方式调用Sobject的情况下,返回错误。例如,对于代码“Sobject1.prototype.x1=5”,由于是使用原型读写的方式调用Sobject的,因此会产生运行时错误。
【内存模型】
多线程的每个线程拥有一个私有内存堆(Thread Local Heap,可称为“第二内存堆”),仅能够被单个线程调用的第二对象存储在其对应的线程所拥有的第二内存堆内。能够被多个线程共享的Sobject存储在共享内存堆(Shared Heap,可称为“第一内存堆”)中。
也就是说,在程序运行过程中国,可以为每个线程维护一个私有内存堆,线程的私有对象(第二对象)可以存储在其私有内存堆内。并且,可以维护一个对应于全局的共享内存堆,能够被多个线程共享的对象(第一对象)可以存储在该共享内存堆内。
第一对象被设置为仅能够引用其它第一对象。第二对象被设置为能够引用第一对象,并且还可以引用被该第二对象对应的线程调用的其它第二对象。也就是说,第二对象的属性允许指向第一内存堆中的Sobject,而Sobject的属性只允许指向第一内存堆中其它的Sobject。
【封闭性】
如图2所示,可以分别为线程1维护第二内存堆1,为线程2维护第二内存堆2。并且可以维护一个多个线程共享的第一内存堆。Sobject对象存储在第一内存堆中。每个第二内存堆内可以存储一个或多个仅能够被该线程调用的第二对象。不同线程的第二内存堆内的第二对象之间相互隔离,无法相互访问。第二对象可以访问Sobject对象,但是Sobject对象不能访问第二对象。并且,Sobject是去除原型链化后的对象,而第二对象可以是正常能够使用原型链的方式实现继承的对象。
可见,Sobject本身的任何属性不允许指向任何第二对象。第一内存堆内的Sobject只能引用其它Sobject,不能引用第二对象。第二对象可以引用Sobject对象,并且,第二对象可以引用被该第二对象对应的线程调用的其它第二对象。
Sobject的封闭性使得,单个线程的第二内存堆内的第二对象不能访问到其他线程的第二内存堆内的第二对象。由此,可以保证调用第二对象的线程的安全,从而把共享同步问题限制在Sobject范围内。可以通过插入封闭性违例检测的方式检测Sobject是否引用了第二对象,具体参见下文描述。
另外,由于Sobject没有原型链读写的问题,因此可以采用和Java语言类似的采用synchronized的方式进行同步。此处不再赘述。
【第一对象的内存分配】
用户在使用缺省的new Classname时,对象是分配在局部对象区(即本公开述及的第二内存堆),和JS标准语义一致,比如官方webworker api只有thread local的内存区,并没有共享对象区,用户使用扩展的JS编程时,使用关键字sobject时,是分配在共享对象区(即本公开述及的第一内存堆),共享对象区是标准JS所没有的。
图3是示出了第一对象的内存分配流程的示意图。
参见图3,在步骤S310,对于识别为第一对象的程序代码,例如对于New SobjectMyclass()方法,可以由编译器分析New Sobject Myclass(),生成AST节点,如可以生成New Sobject tree node。
在步骤S320,将第一对象对应的AST节点翻译为相应的字节码,如可以由编译器/解释器/字节码生成器将New Sobject tree node翻译为相应的字节码。
在步骤S330,在第一内存堆中为第一对象分配所需的内存空间。
对于第一对象,可以遍历AST,根据节点间的继承关系,确定其原型链,以将原型链中该第一对象的属性提取出来,作为第一对象的一部分,然后根据第一对象的所有属性数据,在第一内存堆中为第一对象分配相应的内存空间,以将该第一对象的所有属性存储到所分配的内存空间中。
【第一对象的构造】
为了保证第一对象的封闭性,可以对第一对象的读写方式进行重新构造。第一对象的构造过程主要包括两个步骤:一是为第一对象的属性换名;二是为第一对象的各个属性,生成同名的getter方法和setter方法,在getter方法和/或setter方法内插入封闭性违例检测。由此,在调用Sobject1.x时,其实是在调用Sobject1的getter x,无法直接获得Sobject1的属性x,因为x已经换名。因此,可以放心地对第一对象的属性进行读写,而不用担心第一对象会引用到普通对象(即第二对象)。并且,对于第二对象的属性的读写不需要插入任何封闭性违例检测,这样使得单线程的性能可以达到零损失。
图4是示出了第一对象的构造流程示意图。
参见图4,在步骤S410,为第一对象生成用户不可见的静态构造函数。静态构造函数不允许修改原型。第一对象的所有属性可以包含在构造的函数内。
在步骤S420,为静态构造函数的属性进行换名。
静态构造函数中的属性也即第一对象的属性,可以对第一对象所拥有的各个属性进行换名,以使得换名后的属性名称不同于之前的名称。
在步骤S430,生成和属性同名的getter方法和setter方法。
此处主要是生成和换名前第一对象(也即静态构造函数)的属性同名的getter方法和setter方法。通过getter方法可以实现对第一对象的属性的读操作,通过setter方法可以对第一对象的属性进行写操作,getter方法和setter方法为本领域现有技术,此处不再赘述。
在getter方法和setter方法内可以插入封闭性违例检测,可以用于在运行过程中,检测第一对象是否引用了第二对象。并且,在检测到第一对象引用了第二对象的情况下,可以报错。
封闭性违例检测示例如下。
(1)new操作时把普通对象man以参数的形式付给Sobject women
class Person{
constructor(name){
this.name=name;
print('In constructor:Hi,I am'+this.name);
};
};
class Wife{
constructor(name,hus){
this.name=name;
this.husband=hus;
print('In constructor:Hi,I am'+this.name+'my husband is'+this.husband.name);
};
};
function foo(){
var man=new Person("Mark");
var woman=new Sobject(Wife("Mary",man));
}
编译器会报下面的错误:
TypeError:Cannot set property'husband'of Sobject#<Person>to AJSobject
var woman=new Sobject(Wife("Mary",man));
(2)把JS对象付给Sobject的属性
class Person{
constructor(name){
this.name=name;
this.relative=undefined;
print('In constructor:Hi,I am'+this.name);
};
};
function foo(){
var man=new Person("Tom");
var woman=new Sobject(Person("Mary"));
woman.relative=man
}
编译器会报下面的错误:
TypeError:Cannot set property'relative'of Sobject#<Person>to AJSobject
woman.relative=man
在步骤S440,冻结原型链。
此处述及的冻结原型链主要是指,在对第一对象的属性进行读写操作时,不再支持原型链的读写方式,而可以直接从为第一对象分配的内存空间内读取第一对象的属性。
应用例
主线程可以以person为class构造一个第一对象,并把第一对象传递给worker。
class Person{
constructor(name){
this.name=name;
};
};
var women=new Sobject(Person(“Mary”));
//把women对象传递给worker;
worker.postMessage(women);
//接收worker传回的women对象。
worker.onmessage(event){
var women=event.data;
synchronized(women){
women.age=38
print(“my name is”+women.name);
}}
worker里改写women的name属性,并使用synchronized关键字同步。
onmessage=function(env){
var women=env.data;
synchronized(women){
women.name=“Honey”;
postMessage(women);
}}
主线程在接受到worker发回的消息后调用worker.onmessage响应消息并打印结果
print(“my name is”+women.name);
【优化装置】
图5示出了根据本公开一实施例的针对动态类型语言的优化装置的结构示意图。其中,优化装置500的功能模块可以由实现本发明原理的硬件、软件或硬件和软件的结合来实现。本领域技术人员可以理解的是,图5所描述的功能模块可以组合起来或者划分成子模块,从而实现上述发明的原理。因此,本文的描述可以支持对本文描述的功能模块的任何可能的组合、或者划分、或者更进一步的限定。
下面就优化装置500可以具有的功能模块以及各功能模块可以执行的操作做简要说明,对于其中涉及的细节部分可以参见上文描述,这里不再赘述。
参见图5,优化装置500包括属性提取模块510和属性保存模块520。
属性提取模块510用于对于基于动态类型语言编写的程序代码中的至少一个第一对象,从第一对象所在的原型链中提取第一对象的属性。属性保存模块520用于将属性作为第一对象的一部分进行存储。例如,属性保存模块可以将属性存储在为第一对象分配的存储空间。
作为本公开的一个示例,优化装置还可以可选地包括加锁模块530。加锁模块530用于在对所述第一对象的属性执行读和/或写操作的情况下,仅对该第一对象进行加锁处理。
本公开的述及的第一对象可以在所述程序代码运行过程中能够被至少两个线程调用的对象。可以设置为在运行过程中使用原型链的读写方式调用第一对象的情况下,返回错误。
作为本公开的一个示例,优化装置还可以可选地包括换名模块540和/或生成模块550。换名模块540用于对所述第一对象具有的属性进行换名。生成模块550用于为所述第一对象具有的属性生成同名的getter方法和setter方法。
作为本公开的一个示例,优化装置还可以可选地包括插入模块560。插入模块560用于在所述getter方法和/或所述setter方法内插入封闭性违例检测,所述封闭性违例检测用于检测所述第一对象是否引用了第二对象,其中,所述第二对象为所述程序代码的运行过程中仅能够被单个线程调用的对象。
在本公开中,第一对象被设置为仅能够引用其它第一对象,第二对象被设置为能够引用第一对象以及被第二对象对应的线程调用的其它第二对象。第一对象存储在第一内存堆中,第一内存堆中的对象能够被多个线程调用,并且第二对象存储在调用其的线程对应的第二内存堆中,第二内存堆中的对象仅能够被其对应的线程调用。关于第一对象和第二对象,可以参见上文相关描述,此处不再赘述。
【计算设备】
图6示出了根据本公开一实施例可用于实现上述针对动态类型语言的优化方法的数据处理的计算设备的结构示意图。
参见图6,计算设备1000包括存储器1010和处理器1020。
处理器1020可以是一个多核的处理器,也可以包含多个处理器。在一些实施例中,处理器1020可以包含一个通用的主处理器以及一个或多个特殊的协处理器,例如图形处理器(GPU)、数字信号处理器(DSP)等等。在一些实施例中,处理器1020可以使用定制的电路实现,例如特定用途集成电路(ASIC,Application Specific Integrated Circuit)或者现场可编程逻辑门阵列(FPGA,Field Programmable Gate Arrays)。
存储器1010可以包括各种类型的存储单元,例如系统内存、只读存储器(ROM),和永久存储装置。其中,ROM可以存储处理器1020或者计算机的其他模块需要的静态数据或者指令。永久存储装置可以是可读写的存储装置。永久存储装置可以是即使计算机断电后也不会失去存储的指令和数据的非易失性存储设备。在一些实施方式中,永久性存储装置采用大容量存储装置(例如磁或光盘、闪存)作为永久存储装置。另外一些实施方式中,永久性存储装置可以是可移除的存储设备(例如软盘、光驱)。系统内存可以是可读写存储设备或者易失性可读写存储设备,例如动态随机访问内存。系统内存可以存储一些或者所有处理器在运行时需要的指令和数据。此外,存储器1010可以包括任意计算机可读存储媒介的组合,包括各种类型的半导体存储芯片(DRAM,SRAM,SDRAM,闪存,可编程只读存储器),磁盘和/或光盘也可以采用。在一些实施方式中,存储器1010可以包括可读和/或写的可移除的存储设备,例如激光唱片(CD)、只读数字多功能光盘(例如DVD-ROM,双层DVD-ROM)、只读蓝光光盘、超密度光盘、闪存卡(例如SD卡、min SD卡、Micro-SD卡等等)、磁性软盘等等。计算机可读存储媒介不包含载波和通过无线或有线传输的瞬间电子信号。
存储器1010上存储有可执行代码,当可执行代码被处理器1020执行时,可以使处理器1020执行上文述及的优化方法。
【方案总结】
动态类型语言(Dynamic-typed)的语言的多线程支持是理论和技术上的难点。以JS为例,Web Worker是HTML5标准的一部分,这一规范定义了一套API,它允许一段JS程序运行在主线程之外的另外一个线程中。Web Worker本质上是线程数据隔离,就是一个线程内无法引用到其他线程的任何JS对象,只能通过消息在线程间传递。这样的模型能满足一部分多线程需求,但是对很多较为复杂的共享,比如链表和tree,复杂的对象这样的数据结构无法共享和同步。
目前并没有对象共享的多线程方案,因此很难解决传统的多线程开发模式。现有的node.js异步模式,本质上在JS层还是单线程程序,Node.js的loop,在频繁响应native消息的情况下会阻塞主线程,造成主线程卡顿。
有鉴于此,针对动态类型语言,本公开提出了一种“去原型链化”的构思,通过从原型链中提取对象的属性,并将其存储在对象本身上,可以使得对象被调用时,无需锁定整个原型链。
基于这种“去原型链化”的构思,本公开进一步提出了在同一个Runtime(运行时)下存在的两套对象系统,一套是现有的普通对象(即上文述及的第二对象),一套是去除原型链读写属性的共享对象(即Sobject)。通过对Sobject的内存模型、封闭性的设定,使得在保证普通对象的线程安全的同时,把线程同步问题限制在Sobject的范围内。
本公开可以应用在多种动态类型语言上,如可以应用在Javascript上,使得Javascript达到真正意义上的共享JS对象(禁止原型链操作的JS对象),扩展了现有JS多线程使用webworker的使用场景。解决web worker不能解决JS对象共享的问题。
通过禁止sobject原型链操作,使得sobject可以采用和Java类似的同步机制进行同步。并和现有web worker方案相兼容,可以认为是web worker做了sobject共享对象的扩充。
上文中已经参考附图详细描述了根据本发明的针对动态类型语言的优化方法、装置以及计算设备。
此外,根据本发明的方法还可以实现为一种计算机程序或计算机程序产品,该计算机程序或计算机程序产品包括用于执行本发明的上述方法中限定的上述各步骤的计算机程序代码指令。
或者,本发明还可以实施为一种非暂时性机器可读存储介质(或计算机可读存储介质、或机器可读存储介质),其上存储有可执行代码(或计算机程序、或计算机指令代码),当所述可执行代码(或计算机程序、或计算机指令代码)被电子设备(或计算设备、服务器等)的处理器执行时,使所述处理器执行根据本发明的上述方法的各个步骤。
本领域技术人员还将明白的是,结合这里的公开所描述的各种示例性逻辑块、模块、电路和算法步骤可以被实现为电子硬件、计算机软件或两者的组合。
附图中的流程图和框图显示了根据本发明的多个实施例的系统和方法的可能实现的体系架构、功能和操作。在这点上,流程图或框图中的每个方框可以代表一个模块、程序段或代码的一部分,所述模块、程序段或代码的一部分包含一个或多个用于实现规定的逻辑功能的可执行指令。也应当注意,在有些作为替换的实现中,方框中所标记的功能也可以以不同于附图中所标记的顺序发生。例如,两个连续的方框实际上可以基本并行地执行,它们有时也可以按相反的顺序执行,这依所涉及的功能而定。也要注意的是,框图和/或流程图中的每个方框、以及框图和/或流程图中的方框的组合,可以用执行规定的功能或操作的专用的基于硬件的系统来实现,或者可以用专用硬件与计算机指令的组合来实现。
以上已经描述了本发明的各实施例,上述说明是示例性的,并非穷尽性的,并且也不限于所披露的各实施例。在不偏离所说明的各实施例的范围和精神的情况下,对于本技术领域的普通技术人员来说许多修改和变更都是显而易见的。本文中所用术语的选择,旨在最好地解释各实施例的原理、实际应用或对市场中的技术的改进,或者使本技术领域的其它普通技术人员能理解本文披露的各实施例。