一:引言
数据驱动的编程方法可以根据不同的数据类型灵活的控制执行逻辑,但目前这种编程方法中并未引入调度技术,待执行的逻辑序列固定编码在执行程序中,虽然可以将数据与过程的对应关系抽取出来,单独放在数据库或者采用类似脚本的动态语言进行控制增加灵活性,但如果数据与过程的对应关系不能固定(比如需要根据某种规则决定执行的过程),或者过程执行需要存储状态(比如需要等待其它数据或者挂起等),现有的处理方法不再适用,需要引入调度机制。
数据驱动的过程调度包含数据对象,过程(函数)对象和调度器,数据和过程(函数)作为对象可以被调度,过程(函数)不再固定的位于某个或者某些逻辑处理序列中。过程对象的调度由数据对象驱动,具有对输入和输出数据对象类型的表述和用于调度的上下文。过程对象的描述类似于函数原型定义,只不过在函数原型定义中,输入和输出的类型未限定,可能过于泛化(比如:函数输入字符串,这个字符串可能具有众多含义,可能是URL,可能是格式化数据,可能是普通文本,或者具有某些含义的序列),而且函数执行逻辑的过程中与环境之间没有约束(比如:引用或者修改全局变量),其本身也不具备调度相关的状态记录,导致可调度能力非常弱,目前的调度大多都是基于线程粒度的调度。很多现代编程语言支持反射方法,可以获取函数原型的表述,虽然实现起来复杂一些,但可以支持函数的调度,不过,由于缺乏或者没有明确限定输入和输出数据对象的含义,以及调度相关的上下文,通过反射机制实现的调度,缺乏灵活性,需要严格的逻辑控制。另外一种微服务编程方法是将复杂逻辑拆分成为小的服务单元,但其提出的初衷和目的更多是为了降低发布维护升级的复杂性和提升效率,而不是微服务本身的调度。
调度器根据数据对象的类型,查找能够处理该数据对象的过程(函数)对象,将数据对象分配给过程对象执行,获得输出的数据对象。在查找过程对象时,可以设置期望的输出数据对象类型,如果不存在单个过程输出该类型,可以根据过程对象的输入和输出对象类型,查找输出目标数据对象类型的过程对象序列,多个过程对象序列形成一个过程链,处理输入数据对象,获取期望的输出数据对象。
过程链编程pchain(process chain) programming实现了这里提到调度和编程方法,目前支持python语言,可以通过pip安装
pip install pchain
二:定义数据对象
数据应该具有类型,如果数据失去类型,则失去了数据原本的含义,此时,这种数据的处理只能由既定的逻辑序列进行处理,因为只有设计者清楚数据的含义,不能做到自动的调度处理。支持大量数据对象类型的灵活定义具有客观的意义,正如我们所生活的世界,昆虫的种类就有上百万种。
类比传统的调度具有线程的上下文一样,对数据对象进行调度也需要上下文,记录数据对象已经分配给了哪些过程,防止数据对象重复分配,如下图所示:
过程对象列表记录数据对象已经分配给的过程对象。Data指向具体的数据内容或者值。数据对象增加调度上下文之后,可以记录更多地信息,比如源对象列表:表明该数据对象依赖于哪些对象生成;所有者过程对象: 表明该数据对象是由哪个过程对象生成的,这些记录体现数据之间的关系,以及数据与过程之间的关系。
编程语言支持的类型是原生类型,比如整形,字符串,浮点类型等等。这些类型不具有特定的含义,比如整形,可以代表年龄、重量、尺寸等等。编程语言支持定义结构或者类,这些定义的结构和类具有特定的含义,其实例可以作为数据对象,成为其它过程(处理函数)的输入数据或者输出数据。目前编程语言中定义的类缺少这种支持调度的上下文管理结构,无法记录该数据对象是否已经分配给了某个过程对象,导致不能实现数据对象的自动分配和调度,因此需要一种新的数据对象的定义方法。
在pchain实现中,python数据对象的定义由pydata模块支持,通过DefineType/DefineSubType两个函数实现,在定义python数据对象类型时,可以指定原生的python类型。
DefineType(tpname,rawtype=None)
DefineSubType(parenttype,tpname,rawtype = None)
例如:
pydata.DefineType("NumberClass",float)
pydata.DefineSubType(NumberClass,'IntClass')
创建数据对象的实例时指定数值或者原生实例,数据对象创建之后,其内容不应该修改,可以调用Lock方法。如果数据对象对应着一个python类的原生实例,这个方法不能阻止实例中属性的修改,需要使用的时候保证。
数据对象不仅记录着已经分配给了哪些过程,支持调度器的数据对象分配过程,而且可以记录数据对象之间的关系,包括该数据对象是由哪个过程对象产生的,是基于哪些数据对象产生的,以及该数据对象产生了哪些数据对象。这些关系可以用于提取数据对象相关的规则。为支持规则系统,数据对象需要有唯一的关键字,将数据对象与规则网络中的节点一一对应,通过GetTag函数可以得到数据对象的关键字。
d = NumberClass(12.3)
key = d.GetTag()
print(key)
输出结果为:
6d5450b62cb0b8d2b4c0ace38eef4b4df697129e
数据对象可以存储为JSON字符串,并能够在其它电脑上恢复,实现共享。
buf=Service._ServiceGroup._NewParaPkg()
realm.SaveObject(buf,d)
val = buf._ToJSon()
print(val)
输出结果为:
{"PackageInfo":[],"ObjectList":[{"PackageInfo":[],"Value":[null,"gANHQCiZmZmZmZou"],"ClassName":"NumberClass"}]}
三:定义过程对象
过程对象包含描述输入输出数据类型的说明部分和逻辑执行部分。过程对象的输入和输出必须是前述的数据对象,基于这些数据对象拥有的管理结构,才能够实现基于数据对象的调度。如果过程的某个输入不是数据对象,则调度器无法确认什么数据对象可以分配给该过程,该过程对象的输出如果不是数据对象,则该过程产生输出之后,调度器无法对输出数据进一步处理。
过程在执行过程中,存在不能一次完成全部执行的情况,比如过程可能数据不完整,需要更多的数据对象,也可能需要重复执行或者由于等待而挂起,因此过程对象同样需要用于调度的上下文,现有过程(函数)的定义方法并不支持,需要定义新的过程对象。
在pchain中,python过程对象的定义由pyproc模块支持,通过DefineProc函数实现。
pyproc.DefineProc(tpname,InputDataType,OutputDataType,PyFunc)
例如:
@pyproc.DefineProc('HelloWorldProc',None,None)
def Execute(self) :
print('Hello world !')
return (0,1,None)
过程对象在使用时,需要需要创建实例。采用上述方法定义的过程对象为python过程对象,同样对应有CLE过程对象,负责记录过程执行的上下文
cle_p = HelloWorldProc().Wrap()
过程对象同样具有唯一的Tag,该Tag通过GetTag函数获得,并且所有实例的Tag相同。
key = cle_p.GetTag()
print(key)
输出结果为:
proc_global_HelloWorldProc
过程对象可以存储为JSON字符串,并能够在其它电脑上恢复,实现共享。
buf=Service._ServiceGroup._NewParaPkg()
realm.SaveObject(buf,cle_p)
val = buf._ToJSon()
print(val)
输出结果为:
{"PackageInfo":[],"ObjectList":[{"ClassName":"HelloWorldProc","ObjectID":"fa3d7eb9-5720-490f-a47a-b7a0103c6e38","Type":"PCProc"}]}
四:调度执行
将数据对象和过程对象放在一起,就可以调度执行了。在pchain中,由cell对象管理调度,在执行时,将cell加入到realm中执行,可以在realm中加入多个cell执行。下面是一个简单的例子,该例子是从键盘输入两个数字,计算它们的和:
1. 定义数据对象
pydata.DefineType('NumberClass')
2. 定义过程对象
这里定义两个过程对象,一个过程对象负责从键盘输入数字,一个过程对象负责计算两个数字的和。
a. 输入过程对象,
该过程对象不需要输入数据对象,是从键盘输入;输出一个数据对象。定义如下:
@pyproc.DefineProc('InputProc',None,NumberClass)
def Execute(self) :
Context = self.Context
if Context['SelfObj'].Status < 0 :
return None
val = input('input a number : ')
return (4,1,NumberClass(val))
b. 求和过程对象,
该过程对象输入两个数据对象,计算两者之和后打印出来,没有输出数据对象,定义如下:
@pyproc.DefineProc('OutputProc',(NumberClass,NumberClass),None)
def Execute(self,num1,num2) :
Context = self.Context
print('sum = ', num1.value() + num2.value())
Context['Cell'].Finish()
return (0,1,None)
在执行完成之后,调用Cell.Finish()函数结束执行,并退出。
3. 创建Cell, Realm,将过程对象加入到Cell中执行
cell = Service.PCCellBase()
cell.AddProc(InputProc,OutputProc)
realm.AddCell(cell)
realm.Execute()
执行结果如下:
input a number : 1
input a number : 2
sum = 12
4. 调度过程说明
调度分为两个步骤:数据对象分配和过程对象执行。首先处理数据对象的分配,如果数据能够分配给某个过程,则创建该过程的执行实例,将数据对象分配给这个过程,然后依次调度执行。
在上述例子中,cell中有两个过程,由于'InputProc'不需要输入数据,满足调度条件,可以调度执行,'OutputProc'需要两个NumberClass对象作为输入,由于此时cell中没有数据对象,因此不满足调度条件,第一次调度只执行'InputProc'。
'InputProc'执行会从键盘上输入一个数据,将其转换成数字对象之后,放到Cell中。进行下一轮调度,此时'OutputProc'仍然不满足调度条件,只能再次执行'InputProc'输入一个新的数字对象。
Cell中有两个数字对象了,'OutputProc'可以调度执行,'OutputProc'计算两个数字的和,打印到屏幕上,然后调用Cell.Finish()函数结束执行,并退出。
五:结论
通过定义数据对象类型和过程对象类型,增加用于支持调度的参数,可以实现数据驱动的过程调度方法。在该编程方法中,过程对象与目前各种编程语言支持的过程(函数)不同,过程对象是一个可以被调度和分配的对象,不再是执行代码中的固定逻辑序列。这种方法具有极大的灵活性,构造性和共享性。
- 灵活性,如果数据对象需要进行某些处理,在任何时候,都可以将数据对象和过程对象放在一起,即可获取处理的结果。
- 构造性,根据输入数据对象和输出数据对象类型,可以获取得到输出数据的一个或者多个过程序列,实现类似自动编程的功能
- 共享性,在上述例子中并未体现,过程对象和数据对象都可以存储成为JSON格式的字符串,可以在其它计算单元上恢复,并执行。
每个过程对象和数据对象都具有唯一一个Tag,可以作为Key,将对象映射到图网络中的节点或者数据库,可以由图网络的规则系统指导过程链的构造。在任何时候,都可以创建cell,调度数据和过程对象执行。
来源:CSDN
作者:srplab1
链接:https://blog.csdn.net/srplab1/article/details/103650843