Parallet - My Dynamic Language - 一款异步编程语言
简介:
Parallet是笔者自创的一种新的编程语言. 当初的定位是DotNet下的异步脚本, 用来弥补C#对异步编程的不足. (笔者想实现一些异步操作超多超复杂的服务器应用, 但是用C#做起来超难. )
就在昨天, 笔者完成了初步的动态编译到IL的实现. 让大部分不需要异步执行的函数, 编译成CLI的方法. 这些函数由解释执行转换为编译执行后, 性能提高了100多倍. (解释执行的性能和VBScript差不多)
基本上, 现在很多架构上的设计, 都已经完成. 大方向比较明确, 剩下的, 都是无穷无尽的细节问题.
异步:在异步编程方面, 由于新语言的异步函数的特点, 让它除了支持传统的命令式编程, 还支持新的异步方式编程, 让开发者可以用传统的命令式语法去编写异步操作, 一次过摆脱为了异步而异步的各种麻烦的编程手法.
而且, 这个新的语法体系里, 函数默认是异步的, 系统会自动分析哪些函数需要异步处理, 而哪些不需要. 所以, 这个语言并没有为异步操作而创建任何一个关键字. 非常简单容易接受.
实际上一时之间要描述出这个新的异步语言, 是非常非常难. 这表现在多个方面:
- 笔者的语文水平极差, 表达能力极差, 心里所想的东西并不能很好地表达出来.
- 和传统编程一摸一样的语法, 这是优点, 但也导致读者会认为这根本就和异步编程没什么关系..
- 如果读者没有做过异步编程的工作, 那么就无法知道异步编程有多麻烦,
不知道异步编程的麻烦之处, 也不会明白异步函数调用异步函数的好处.如果说代码就是文档的话, 那么或许用代码来解释这个异步函数的概念, 可能是最好的. 以下就用例子来慢慢说明异步函数的概念与意义.
示例:以下示例, 力求简单, 都是随便手写, 用JS语法, 目的是让大家更容易理解.
传统,阻塞型,同步调用
function LoadConfig(){ var xhr=new XMLHttpRequest(); xhr.open("GET","config.aspx",false); xhr.send(""); return ParseConfig(xhr.responseText);} 上面代码, 通过给xhr.open传递一个false, 指定为sync模式. 当xhr.send("")执行时, 除非服务器返回或者出错, 否则当前代码就会被阻塞, 当前的线程也会被阻塞, 浏览器也不会响应.
在UI线程里, 发生阻塞, 界面假死, 对用户是非常不友好的事. 为了解决这个问题, xhr提供了异步模式
传统,回发型,异步调用:
function LoadConfig(){ var xhr=new XMLHttpRequest(); xhr.open("GET","config.aspx",true); xhr.onreadystatechange=function() { if(xhr.readyState<4)return; ParseConfig(xhr.responseText); } xhr.send("");} 通过给xhr.open传递一个true, 指定为async模式. xhr.send("")后, 就立刻返回, 不会发生阻塞, 但程序也不会立刻得到结果. xhr得到服务器的响应后, 执行onreadystatechange指定的方法, 来通知它本身的调用者去处理异步的结果.
咋一看, 两个LoadConfig用了不同的方式, 实现了相同的事了不 ? 没有 !
假如有人写了这么一段代码 :
var config=LoadConfig();InitMySystem(config.OptionName); 阻塞方式的LoadConfig能阻塞线程, 等待服务器回复, 并且把结果返回给它的调用者.
但是, 如果把LoadConfig里的xhr改成异步调用, 那么config=LoadConfig()就不能正确地给config传递服务器结果了.
所以, 要解决这个问题, 方案是建立新的函数 LoadConfigAsync
function LoadConfigAsync(callback){ var xhr=new XMLHttpRequest(); xhr.open("GET","config.aspx",true); xhr.onreadystatechange=function() { if(xhr.readyState<4)return; callback(ParseConfig(xhr.responseText)); } xhr.send("");} 而在外面的代码, 也需要响应改变成
LoadConfigAsync(function(callback){ InitMySystem(config.OptionName);}); 这种方式, 就是目前大多数编程语言的现状. 无论是JS,还是win32下的异步socket,还是DotNet的BeginXxx/EndXxx, 都是通过传递回发函数(事件也是传递回发函数的形式).
这种方式, 一旦函数之间的嵌套比较深入, 就会变得非常复杂, 而且它很难实现各种循环, 更难实现的, 就是如何处理异步错误. 很多时候, 代码给拆分出非常多块, 基本上每一小块都要try/catch, 处理异常后要返回也是非常麻烦.
所以, 传统的异步编程, 非常不直观, 非常麻烦. 这就是Parallet希望能解决的问题.
在Parallet里, 所有的函数, 默认都是异步函数. 开发者不需要考虑阻塞不阻塞的问题, 这是语言需要帮开发者解决的问题.
上面的问题, 在Parallet是这样写的 : (语法形式,XHR为虚构)
//PARALLET代码function LoadConfig(){ var xhr=new XHR(); xhr.open("get","server.aspx"); xhr.send(""); return ParseConfig(xhr.responseText);}var config=LoadConfig();InitMySystem(config.OptionName); 在Parallet里, xhr.open不再需要指定同步和异步. 因为默认就是异步. 但是, xhr.send("")之后, 服务器没有返回, 调用链会被挂起, 不会阻塞当前线程. 当前线程会继续跑去执行其他事情. 当server.aspx返回后, 当前线程又会跑回来, 继续执行ParseConfig的部分, 然后把LoadConfig的结果返回给config变量, 继续执行外面的代码.
可以看出, Parallet的主要思想, 就是释放当前线程, 去干别的事, 然后再回来继续执行代码.
这个看上去好像很简单, 但是目前的主流语言都无法直接在CPU指令或IL的层次上实现它. 主要原因就是目前的计算机体系, 是用stack来储存函数调用链上的各种参数返回值和临时变量的. 如果要实现函数执行到一半就释放线程, 那么操作系统就必须要把stack上的数据全部储存起来, 当需要恢复函数的执行时, 又需要把数据恢复到stack上, 然后让线程继续.
而Parallet也一样, 并没有在IL上实现它, 所以Parallet执行异步函数时, 只能解释执行, 所有的参数返回值临时变量, 都是储存在虚拟机的特殊stack上, 而不是线程的stack. 也就是说, 不存在这个保存和恢复数据的过程, 但性能的损失也是非常多. 基于这个解释型的虚拟机, 任何函数都能随时挂起, 线程可以随时切换去做其他的事.
也许大家还没有看到实质的好处, 因为例子实在太简单. 如果在外面套一层try/catch, 就能看得出异步函数的优越性了
//PARALLET代码function LoadConfig(){ var xhr=new XHR(); xhr.open("get","server.aspx"); xhr.send(""); return ParseConfig(xhr.responseText);}try{ var config=LoadConfig(); InitMySystem(config.OptionName);}catch(x){ alert("initialize failed.");} 上述的代码,无论在异步操作的任何环节出错, 错误都能被统一catch住. 如果是换成传统的LoadConfigAsync, 要让多个被分割的函数里统一处理错误? 非常难..非常难...(无限回音...) , 这也是笔者用C#来编写那个服务器应用时最恼火的事.
最后
上面用代码来说明了一下Parallet和传统异步编程的区别. 可以看出,Parallet能把异步问题大大地简化掉.
没有新语法,没有新关键字,一切都是那么的简单, 自然..
在这个模式下, 有很容易的方式去处理各种异步,并发, 多线程的综合问题. 这个笔者会陆续补充上.
补充:软件开发 , C语言 ,