这玩意自动插的br会和pre在iE6下发生奇特的BUg. 算了,以后还是全手工来吧。
2009年3月31日星期二
2009年3月29日星期日
[+/-] |
线程上下文友好的RPC |
最近工作需要,把系统重新构造为多个processes。以前的api调用有部分转换为RPC。因此需要做一个RPC的模块原型。RPC的基本实现一般操作系统上都有介绍,最简单的模型是(基于Message的同步RPC)
Client: ... rpc_call(){ send(api_id, args); recv(ret); return ret; }
Server: while(...) { recv(api_id, args) ret = call(api_id, args); send(ret); }
如果server只有一个线程,那么所有的RPC call都是同步的。一般不会这么简单的去使用,于是Server会创建线程来来处理,可能是为每个调用,也可能为每个连接。这也是目前看到一些流行RPC的做法,也是KISS的做法。可是,当你不是去做新东西的,而是为了改造以前的东西的时候,这些KISS的就不能方便的直接用了。 稍微复杂一点,双向的RPC调用的处理。普通函数调用经常也会有callback的情况。也就是说双方都会成为Server。如果对方发送callback的RPC调用到当前调用者,如何处理会比较好。简单的应用上面的模型,callback会在调用者进程某个处理RPC的线程内处理。如果在这个Callback内部又要调用到对方,也就是出现乒乓球一般的你来我往,那么每次跨越进程都会有新的线程产生。这的确不太好,而且还有一个问题,就是普通函数经常会有同步callback,而且这个callback的线程环境需要是和caller相同的环境。当有这样的假设时候,用户就可能在一些递归的锁上出问题,比如递归的Mutex.以前OK的code因为线程环境的更换而死锁。(不过,的确用户不应该做这样假设。) 新的简单的做法就是,调用者在发出调用请求后,并不是等待结果,而是进入和server类似的行为,就是和对方做回和,在调用者线程内处理对方的call。
Client: ... rpc_call() { send(api_id, args); while(...) { recv(msg); switch msg type: case ret: return ret; case call: call api from msg } return ret; }
在这里,server的RPC处理函数call内部可以rpc_call回到Client,产生互相的递归调用。同时借用的双方本地堆栈。(一开始我用SystemV的share memory和semaphore,就需要自己维护这个堆栈,稍微麻烦一点,里面的socket操作send和recv都需要用semaphore取代。不过感觉share memory的stack不安全,最后还是放弃了而是使用简单的socket,扩展更好,只是需要一些数据的拷贝开销)。这样,以前同步callback的代码专程RPC之后,线程上下文将保持一至,并且少了重新连接和新建线程的开销。 以上只是示意的伪代码,实际实现还需要不少要考虑的东西。不过我对这个找到这个简单的解法感到满意。程序员不是总是遇到可以随意发挥的时候,面对曾经的那些泥潭,憋屈的时候也能找到coding的乐趣。
2009年3月26日星期四
[+/-] |
一个悖论,两个我 |
有想过一个奇特的问题,就是记忆的复制,好像以前也想过,写下来吧。 假设我们的大脑,或者说我们的意识活动是某个层次上的粒子的活动表现。粒子并不是说一定是某个物理上的概念。比如,如果你的意识是一段程序,那么你的粒子是0和1的组合,定义这个层次是为了一个”可复制“性。这段程序,很显然是可以复制0和1的方式到另外的机器上,独立运行。我们也一定能在这个层次上复制相同的状态过来。也就是说,出现了两个意识,两个我。这两个意识开始同时演绎,将会得到什么呢? 我睡着,冻结意识,复制一份,然后醒过来,”我“将是谁?如果这是成功的,我出现在任何一个新的”我“上都是无法解释的。找一个第三观察者,可以定义真的源头的”我“,和复制出来的”我“。两个”我“醒来后,将对新的”刺激“生成记忆和动作。那么,一开始的论述,相对源头的”我“,将不复存在。就是有多个”我“,世界出现了多个。世界是相对我的,不是绝对的。我看到了另一个”我“,和当前相对我的世界。我的记忆中还有睡着之前的记忆,甚至可以知道我是”真我“,这不重要,重要的是,不是复制了”我“, 而是复制了一个"世界"。 或者,我们的意识不能这样的”确定“和”复制“的, 他是随机的,或者无法冻结,无论如何,无法细分到一个层次定义出可复制的意识。哪怕我们有分子复制,或原子复制机之类的东西。"我"就是独一无二的?
2009年3月21日星期六
2009年3月17日星期二
[+/-] |
“背”算法和写代码 |
高中的时候第一次接触快排的时候,发现即便知道了算法的思想和基本过程,自己写出一个完整的代码,并不是容易的事情。不像做上学题目一样,基本思路有了,基本也就做出来了。当时真的很沮丧,发现写出一个一定程度上可以证明是正确的,都是很难的。当时发现不仅仅是快排,很多程序都不是那么好写,或者说只能写出个样子,却无法放心这样写是不是对的,包括后来的著名的2分查找。当时我就有个疑问,难道学编程,这些基本的东西要背下来么?不会的,不会的。
再后来的日子里,一直伴随着这种失落感,编程接触的也越来越多。有些很容易写出代码,比如动态规划的程序,或者逻辑非常清晰的程序,比如大部分递归的算法。有些很麻烦,基本上要写的时候还要debug,比如一些复杂的数据结构的操作代码,但是这些还算容易从代码上证明基本上正确。最难写的,还是上面提到的某些“短”的代码,比如二分查找和快排之类,如果不知道里面的变量在循环等结构里保持何种关系,所谓循环不变式,只有每次写的时候,从书上认真再去复查,才敢心安。
于是后来开始思考,我要依赖代码重用还是依赖书籍,甚至自己背下来?重用也许是最合理的想法,所以不要再去造轮子。不过,总有一些情况你无法重用曾经的代码,使用一些标准库,然而在很多语言,标准库对于基础算法的封装,并不总是够用的。(Knuth对传统黑箱式的重用就说"有偏见")。另外,从某个意义上,不会造基本的轮子的程序员估计在很多人眼里算不上合格的程序员。
其实大部分情况下都是复制粘贴吧,也许。本质上还是依赖书籍。不过,即便如此,不理解为什么代码这么写,复制粘贴不能解决心中的问题!于是乎,还得把代码的一些关键的地方“背”下来,记下隐藏在i,j后的循环不变式,可能发生的边界问题等等。去年做acm题目的时候,发现大量的时间还是费在一些基本算法的使用上,后来才知道,真正参赛的选手都是要把那些基本算法背的很熟才行的。不过,如何写好算法的代码本身也是个值得思考的问题。一个代码写的好不好,缩进和命名都是表面的,关键是,是不是很尽量能被理解和证明正确性。可读性并不仅仅是文本上的。现实中,我们可能经常看到一些代码,看上去也是工作的,有些复杂,然而,很难看到其中一种条理性:这一段之后,会是什么样的,这个循环每次的入口是个什么样的,我能控制得了。失控的代码,结果总是等到debug的时候修修补补。其实是和自己过不去,代码基本上没办法证明是对的。
我平时写代码还算认真,也喜欢仔细思考每一小段代码的前后(广义上,都算入口和出口)是不是可控的。但是项目一忙,而且很多事情本身就是一个比较让人烦闷的事情,比如merge code,人总是来不及思考,结果让更多的时间给了将来去浪费。
PS,后来,终于在算法导论上看到了快排的一个更优良的划分方式,这个比以前的霍尔的快排要容易理解和证明的多,当时感到十分惊艳,也觉得终于拿掉了当年心中的一个石头。这个算法,好“背”多了。
无论如何,做一个心中合格的程序员还是很难得,还有很长的路。很多人看不起Coder,可是,做一个合格的coder, 也许比很多角色要难很多!而做一个合格的coder,也许是很多所谓光鲜角色的基础,然后,现在看上去很多都是本末倒置。
2009年3月16日星期一
[+/-] |
可恶的Blog css,装修 |
有段时间没更新了,其实我每天都在更新这个可恶的Blog的Html模板。我本来css就不懂,烦死了,好不容易搞了一个满意的,在IE6下面显示有问题,而且问题还都不一样.最后只好还是选择这个最初用的,稍微改改,不敢再改了。 ======分界线=============================== 装修开始了,目前顺利。我负责验收,LP负责买东西。 ======分界线================================