庄周梦蝶

生活、程序、未来
   :: 首页 ::  ::  :: 聚合  :: 管理







posted @ 2010-05-23 05:53 dennis 阅读(855) | 评论 (0)编辑 收藏


1、首先态度需要端正,做代码的自我审查并不是否定自己,而是给自己将工作做得更好的一次机会。在审查过程中要尽量将自己作为一个旁观者的心态去审查自己的代码,尽管这比较困难。

2、代码审查离不开重构,在审查过程中发现任何坏味道都请使用重构去改善,发现缺乏测试的地方要及时补充测试,不要让BUG遗漏。

3、代码的自我审查可能不是越早越好,隔一段时间之后回去看自己写的东西,对一些设计上的选择能有更客观的评价,在审查的过程中可能需要重新去理解代码,在此过程中可以检查自己代码的可读性,并思考如何改善可读性,切记代码首先是给人读的

4、审查过程中需要记录下一些犯下的错误,以及当时为什么会犯下这样的错误,建立自己的bug数据库,并时常review,在以后的工作中避免同样的错误。

5、代码的自我审查应该是一个持续性的过程,而非特定时间的特定行动,时常审查自己的代码,不仅能辨析自己的得失,还能够进一步提高自己在未来工作中的设计能力和预见能力。

6、代码的自我审查跟团队成员之间的相互review并不矛盾,在相互review之前做一个自我审查,有助于提高review的效率,包括可读性的提高和一些一般错误的避免。

7、代码自我审查的一些常见注意点:
(0)自认为绝不会出错,并且从来没有审查过的代码。
(1)注意else语句,if条件下的子语句通常可能是个正常的流程,而else意味着异常的情况或者特殊的场景,你可能特别注意怎么处理正常的情况,却忽略了else子句的实现细节,如该释放的锁没释放,该递减的计数没有递减,该赋予特殊值却没有赋予等等。
(2)注意空的方法,没有方法体的方法,是不需要实现?还是忘了实现?
(3)注意switch语句,有没有忘了break?这种错误通过findbugs之类的静态代码检查工具都能避免。
(4)注意大块的注释,为什么这么多注释?是代码写的很糟糕?还是遗留的注释?遗留的注释会误导人,要及时删除。
(5)注意一些看起来“不合常理”的代码,这样的代码很多都是基于所谓性能考虑而优化过的代码,这样的优化是否还需要?是否能去除这些“奇怪”的代码也能实现正常的需求?
(6)对客户端的使用有假设的代码,假设用户只会这么用,假设用户只会用到返回对象中的某些属性,其他属性一定不会用到?不要对客户代码做假设!这个客户代码包括外部用户和调用这个模块的内部代码。
(7)标注了FIXME、TODO之类task标签的代码,是否忘了修复BUG,实现功能?
(8)任何超过15行以上的方法,这些方法是否能拆分成更细粒度的方法,并保持在同一个抽象层次上?
(9)任何在代码中出现的常量值,是否应该提取出来成为单独的常量,常量的默认值设置是否合理?
(10) 任何持有容器的代码,提供了放入容器的方法,是否提供了从容器中移除对象的方法?确保没有内存泄漏的隐患。
(11)重构中提到的其他坏味道,别放过它们,但是也不要追求完美,OO不是圣杯,如果能简单的实现一个算法,你不要引入3个对象和4个接口。
(12)在review最后能列出一张清单,开列下该项目面临的风险点,并提出解决办法,然后按照这张清单去review关键代码,着重检查异常情况下的处理。风险点的review,我建议可以放在后面,在一般性错误解决之后进行,此时你对代码也将再度变的熟悉。

posted @ 2010-05-18 00:28 dennis 阅读(6402) | 评论 (4)编辑 收藏

    笑死我了


posted @ 2010-04-29 18:54 dennis 阅读(1264) | 评论 (1)编辑 收藏

   
    Ruby Fiber指南(一)基础
    Ruby Fiber指南(二)参数传递
    Ruby Fiber指南(三)过滤器
    Ruby Fiber指南(四)迭代器
    Ruby Actor指南(五)实现Actor

    写这个指南的时候,计划是第五章写一个Fiber的应用例子,但是一时没有想到比较好的例子,模仿《Programming in Lua》中的多任务下载的例子也不合适,因为Ruby中的异步HttpClient跟lua还是很不一样的,体现不了Fiber的优点。因此,这第五节一直拖着没写。
    恰巧最近在小组中做了一次Erlang的分享,有人问到Erlang调度器的实现问题,这块我没注意过,那时候就根据我对coroutine实现actor的想法做了下解释,后来思考了下那个解释是错误的,Erlang的调度器是抢占式的,而通过coroutine实现的actor调度却是非抢占的,两者还是截然不同的。我在《Actor、Coroutine和Continuation的概念澄清》中提到coroutine可以实现actor风格,actor跟coroutine并没有必然的联系,这篇文章的目的就在于证明这一点,使用Ruby Fiber实现一个简单的actor风格的库,整个代码不到100行。后面还会谈到这个实现的缺点,以及我对Erlang调度器实现的理解。

    首先是monkey patch,给Thread和Fiber类加上两个方法,分别用于获取当前线程的调度器和Fiber对应的actor:
class Thread
  
#得到当前线程的调度器
  def __scheduler__
    @internal_scheduler
||=FiberActor::Scheduler.new
  end
end

class Fiber
  
#得到当前Fiber的actor
  def __actor__
    @internal_actor
  end
end

     这里实现的actor仍然是Thread内的,一个Thread只跑一个调度器,每个actor关联一个Fiber。
     让我们来想想调度器该怎么实现,调度器顾名思义就是协调actor的运行,每次挑选适当的actor并执行,可以想象调度器内部应该维护一个等待调度的actor队列,Scheduler每次从队列里取出一个actor并执行,执行完之后取下一个actor执行,不断循环持续这个过程;在没有actor可以调度的时候,调度器应该让出执行权。因此调度器本身也是一个Fiber,它内部有个queue,用于维护等待调度的actor:
module FiberActor
  
class Scheduler
    
def initialize
      @queue
=[]
      @running
=false
    end

    
def run
      
return if @running
      @running
=true
      
while true
        
#取出队列中的actor并执行
        while actor=@queue.shift
          begin
            actor.fiber.resume
          rescue 
=> ex
            puts 
"actor resume error,#{ex}"
          end
        end
        
#没有任务,让出执行权
        Fiber.yield
      end
    end

    
def reschedule
      
if @running
        
#已经启动,只是被挂起,那么再次执行
        @fiber.resume
      
else
        
#将当前actor加入队列
        self << Actor.current
      end
    end

    
def running?
      @running
    end

    
def <<(actor)
      
#将actor加入等待队列
      @queue << actor unless @queue.last == actor
      
#启动调度器
      unless @running
         @queue 
<< Actor.current
         @fiber
=Fiber.new { run }
         @fiber.resume
      end
    end
  end
end

    run方法是核心的调度方法,注释说明了主要的工作流程。因为调度器可能让出执行权,因此提供了reschedule方法重新resume启动调度器。<<方法用于将等待被调度的actor加入等待队列,如果调度器没有启动,那么就启动调度Fiber。

    有了调度器,Actor的实现也很简单,Actor跟Fiber是一对一的关系,Actor内部维护一个mailbox,用来存储接收到的消息。最重要的是receive原语的实现,我们这里很简单,不搞模式匹配,只是接收消息。receive的工作流程大概是这样,判断mailbox中有没有消息,有消息的话,取出消息并调用block处理,没有消息的话就yield让出执行权。

module FiberActor  
  
class Actor
    attr_accessor :fiber
    
#定义类方法
    class << self
      
def scheduler
        Thread.current.
__scheduler__
      end

      
def current
        Fiber.current.
__actor__
      end

      
#启动一个actor
      def spawn(*args,&block)
        fiber
=Fiber.new do
           block.call(args)
        end
        actor
=new(fiber)
        fiber.instance_variable_set :@internal_actor,actor
        scheduler 
<< actor
        actor
      end

      
def receive(&block)
        current.receive(
&block)
      end
    end

    
def initialize(fiber)
       @mailbox
=[]
       @fiber
=fiber
    end

    
#给actor发送消息
    def << (msg)
      @mailbox 
<< msg
      
#加入调度队列
      Actor.scheduler << self
    end

    
def receive(&block)
      
#没有消息的时候,让出执行权
      Fiber.yield while @mailbox.empty?
      msg
=@mailbox.shift
      block.call(msg)
    end

    
def alive?
      @fiber.alive?
    end
  end

end

    Actor.spawn用于启动一个actor,内部其实是创建了一个fiber并包装成actor给用户,每个actor一被创建就加入调度器的等待队列。<<方法用于向actor传递消息,传递消息后,该actor也将加入等待队列,等待被调度。

    我们的简化版actor库已经写完了,可以尝试写几个例子,最简单的hello world:
include FiberActor

Actor.spawn { puts 
"hello world!"}
     输出:
hello world!

    没有问题,那么试试传递消息:
actor=Actor.spawn{
   Actor.receive{ 
|msg|  puts "receive #{msg}"}
}
actor 
<< :test_message
    输出:
receive test_message
    
    也成了,那么试试两个actor互相传递消息,乒乓一下下:
pong=Actor.spawn do
      Actor.receive do 
|ping|
        
#收到ping,返回pong
        ping << :pong
      end
    end
ping
=Actor.spawn do
      
#ping一下,将ping作为消息传递
      pong << Actor.current
      Actor.receive do 
|msg|
        
#接收到pong
        puts "ping #{msg}"
      end
    end
#resume调度器
Actor.scheduler.reschedule

     输出:
ping pong
    
     都没有问题,这个超级简单actor基本完成了。可以看到,利用coroutine来实现actor是完全可行的,事实上我这里描述的实现基本上是revactor这个库的实现原理。revactor是一个ruby的actor库,它的实现就是基于Fiber,并且支持消息的模式匹配和thread之间的actor调度,有兴趣地可以去玩下。更进一步,其实采用轻量级协程来模拟actor风格早就不是新鲜主意,比如在cn-erlounge的第四次会议上就有两个topic是关于这个,一个是51.com利用基于ucontext的实现的类erlang进程模型,一个是许世伟的CERL。可以想见,他们的基本原理跟本文所描述不会有太大差别,那么面对的问题也是一样。

     采用coroutine实现actor的主要缺点如下:
1、因为是非抢占式,这就要求actor不能有阻塞操作,任何阻塞操作都需要异步化。IO可以使用异步IO,没有os原生支持的就需要利用线程池,基本上是一个重复造轮子的过程。
2、异常的隔离,某个actor的异常不能影响到调度器的运转,简单的try...catch是不够的。
3、多核的利用,调度器只能跑在一个线程上,无法充分利用多核优势。
4、效率因素,在actor数量剧增的情况下,简单的FIFO的调度策略效率是个瓶颈,尽管coroutine的切换已经非常高效。

    当然,上面提到的这些问题并非无法解决,例如可以使用多线程多个调度器,类似erlang smp那样来解决单个调度器的问题。但是如调度效率这样的问题是很难解决的。相反,erlang的actor实现就不是通过coroutine,而是自己实现一套类似os的调度程序。
    首先明确一点,Erlang的process的调度是抢占式的,而非couroutine的协作式的。其次,Erlang早期版本是只有一个调度器,运行在一个线程上,随着erts的发展,现在erlang的调度器已经支持smp,每个cpu关联一个调度器,并且可以明确指定哪个调度器绑定到哪个cpu上。第三,Erlang的调度也是采用优先队列+时间片轮询的方式,每个调度器关联一个ErtsRunQueueErtsRunQueue内部又分为三个ErtsRunPrioQueue队列,分别对应high,max和normal,low的优先级,其中normal和low共用一个队列;在Erlang中时间片是以reduction为单位,你可以将reduction理解成一次函数调用,每个被调度的process能执行的reduction次数是有限的。调度器每次都是从max队列开始寻找等待调度的process并执行,当前调度的队列如果为空或者执行的reductions超过限制,那么就降低优先级,调度下一个队列。

   从上面的描述可以看出,Erlang优秀的地方不仅在于actor风格的轻量级process,另一个强悍的地方就是它的类os的调度器,再加上OTP库的完美支持,这不是一般方案能山寨的。
    
    
  

posted @ 2010-04-13 18:31 dennis 阅读(5623) | 评论 (1)编辑 收藏

    在小组内做的一次分享,基本上是在锋爷的一个PPT的基础上做了一些扩充,对Erlang没有了解过的朋友可以看看。

posted @ 2010-04-12 10:45 dennis 阅读(3465) | 评论 (0)编辑 收藏

    基本能说明整个设计以及运行时流程,最近搞文档画的。

基本组件结构图——典型的Reactor+handler模式



一次IO读请求的处理过程:





posted @ 2010-03-26 14:19 dennis 阅读(2268) | 评论 (1)编辑 收藏

妈妈,我不想走
——纪念不幸逝去的孩子们!
妈妈
你为什么在哭泣
今天不是我的生日
为什么在我身旁点燃红烛
没有歌声
也没有小朋友
只有你的泪水
在不停地淌流
妈妈,拽紧我的手
我不想走
妈妈
你为什么在哭泣
今天我又惹你生气
是我没完成作业
还是我偷偷在玩游戏
你打我吧
你骂我吧
千万不要把我抛弃
妈妈,拽紧我的手
我不想走
妈妈
你为什么哭泣
是老天不公
还是你的爱没将我留住
天堂没有阳光
也没有鸟语花香
我不去那边当天使
今生只想做你的心头肉
妈妈,拽紧我的手
我不想走

来自网易网友评论,新闻链接《福建南平男子在校门口杀害八名小学生


posted @ 2010-03-24 09:48 dennis 阅读(1026) | 评论 (0)编辑 收藏


    Actor、CoroutineContinuation这三个概念由于并发的受关注而被经常提到,这里主要想谈下这三者的区别和联系,以便更好的区分问题领域和讨论。首先,Actor和Coroutine在我看来是两种并发模型,仅针对于并发这个领域,而Continuation则是程序设计领域的一个概念,相比于Actor和Coroutine是一个更基础的概念。

    那么,什么是Continuation?这个要从表达式的求值说起。一个表达式的求值可以分为两个阶段:“What to evaluate?”和“What to do with the value”,“What to do with the value”就是计算的Continuation。以下面这段代码为例:
if x<3 then
   
return x+1
else
   
return x
end

    考察其中的表达式x<3,这个表达式就是“what to evaluate?”,代表你将计算的东西,然后根据x<3的结果决定是执行x+1还是直接返回x,这个根据x<3的值来决定下一步的过程就是这个表达式的Continuation,也就是"what to do with the value"。怎么得到某个表达式的Continuation呢?在支持Continuation的语言里提供了call-with-current-continuation的函数,通常简称为call/cc,使用这个函数你就可以在任何代码中得到Continuation。进一步,continuation有什么作用呢?它可以做的事情不少,如nonlocal exits、回溯、多任务的实现等等。例如在scheme中没有break语句,就可以用call/cc实现一些此类高级的控制结构:

(call/cc (lambda (break)
        (
for-each (lambda (x) (if (< x 0) (break x)))
                
'(99 88 -77 66 55))
        #t))


    上面这段代码查找列表(99 88 -77 66 55)中的负数,当查找到的时候马上从迭代中退出并返回该值,其中的break就是一个continuation。刚才还提到continuation可以实现回溯,那么就可以实现一个穷举的机器出来用来搜索解空间,也就是类似Prolog中的回溯机制,在SICP这本书里就介绍了如何用call/cc实现一个简单的逻辑语言系统。更著名的就是神奇的amb操作符,有兴趣可以看看这里
    
     接下来我们来看看如何continuation实现多任务,在Continuation的维基百科里给了一段代码来展示如何用scheme来实现coroutine,我稍微修改了下并添加了注释:
;;continuation栈,保存了等待执行的continuation
(define call/cc call-with-current-continuation)
(define *queue* '())

(define (empty-queue?)
        (null? *queue*))

(define (enqueue x)
        (set! *queue* (append *queue* (list x))))

(define (dequeue)
        (let ((x (car *queue*)))
              (set! *queue* (cdr *queue*))
         x))
;;启动协程
(define (resume proc)
       (call/cc
         (lambda (k)
           ;;保存当前continuation,执行proc
           (enqueue k)
           (proc))))
;;让出执行权
(define (yield)
     (call/cc
      (lambda (k)
         ;;保存当前continuation,弹出上一次执行的cont并执行
        (enqueue k)
        ((dequeue)))))
;;停止当前协程或者当没有一个协程时停止整个程序,最简单的调度程序
(define (thread-exit)
     (if (empty-queue?)
         (exit)
         ((dequeue))))
(注:scheme以分号开头作为注释)

     这其实就是一个coroutine的简单实现,context的保存、任务的调度、resume/yield原语……样样俱全。使用起来类似这样,下面这段程序轮流打印字符串:
(define (display-str str)
        (lambda()
         (let loop()
              (display str)
              (newline)
              (yield)
              (loop))))

;;;创建两个协程并启动调度器
(resume (display-str "This is AAA"))
(resume (display-str "Hello from BBB"))
(thread-exit)

     任务非常简单,打印下传入的字符串并换行,然后让出执行权给另一个任务执行,因此输出:
This is AAA
Hello from BBB
This is AAA
Hello from BBB
This is AAA
Hello from BBB
This is AAA
Hello from BBB
……

    谈了这么多continuation的应用,事实上我想说明的是continuation可以用来实现协程,Ruby 1.9中call/cc和Fiber的实现(在cont.c)大体是一样的同样说明了这一点。

     接下来我们讨论下Actor和Coroutine的关系,上面提到Actor是一种并发模型,我更愿意称之为一种编程风格,Actor跟message passing、Duck Typing是一脉相承的。Actor风格是可以这么描述:将物理世界抽象成一个一个的Actor,Actor之间通过发送消息相互通信,Actor不关心消息是否能被接收或者能否投递到,它只是简单地投递消息给其他actor,然后等待应答。Actor相比于Coroutine是一种更高层次的抽象,它提供的receive和pattern match的原语更接近于现实世界,而使用coroutine编程你还需要手工介入任务调度,这在Actor中是由一个调度器负责的。

    同样,Actor可以用coroutine实现,例如Ruby有个revactor项目,就是利用1.9引入的Fiber实现actor风格的编程,它的实现非常简单,有兴趣地可以看看,其实跟continuation实现coroutine类似。但是Actor并不是一定要用coroutine才能实现,Actor是一种编程风格,你在Java、C#、C++中同样可以模拟这样的方式去做并发编程,.net社区的老赵实现过一个简单的ActorScala的Actor实现是基于外部库,利用scala强大的元编程能力使得库的使用像内置于语言。

    总结下我想表达的:Continuation是程序设计领域的基础概念,它可以用于实现coroutine式的多任务,Actor是一种比之coroutine更为抽象的编程风格,Actor可以基于Coroutine实现但并非必须,Actor和Coroutine都是现在比较受关注的并发模型。



posted @ 2010-03-23 11:49 dennis 阅读(7619) | 评论 (0)编辑 收藏

Google正式退出中国,跟朋友的聊天记录:

: 真到那天,是中国IT业的悲哀
 某人: 不仅仅是it业
  这是一个标志
  铁幕再次拉开
  中国已经不在乎外部形象了
  也不在乎自己的工程师是否能和世界交流了
 只求政权稳定

    能移民的赶紧吧,不能移民的继续被绑架,被代表,被河蟹,喝三鹿,打疫苗,得永生。

posted @ 2010-03-23 09:25 dennis 阅读(1036) | 评论 (2)编辑 收藏

有兴趣地瞧瞧吧,写的匆忙。

posted @ 2010-03-20 17:33 dennis 阅读(3781) | 评论 (4)编辑 收藏

仅列出标题
共56页: First 上一页 9 10 11 12 13 14 15 16 17 下一页 Last