庄周梦蝶

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

   

    开源的Java Memcached Client——Xmemcached 发布1.2.4版本,这个版本主要的工作是BUG修正,主要改动如下:

1、修正bug,包括issue 68,issue 74Issue 68修复后,现在可以正常地使用TokyoTyrantTranscoder来连接TokyoTyrant。

2、为修正的BUG添加新的单元测试。

3、将CachedData.MAX_VALUE修改为可修改状态,允许用户设置更大的值,这个值决定了可以向memcached存储的最大值,默认是1M(通过memcached的-I size选项),单位是字节:

CachedData.MAX_SIZE = 2 * 1024 * 1024 // 修改为2M


4、更正用户指南的错误并补充部分资料。


下载地址: http://code.google.com/p/xmemcached/downloads/list

项目主页:http://code.google.com/p/xmemcached/

Wiki页    :http://code.google.com/p/xmemcached/w/list



posted @ 2010-03-15 13:42 dennis 阅读(2644) | 评论 (6)编辑 收藏


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

     上一节介绍了利用Fiber实现类unix管道风格的过滤链,这一节将介绍利用Fiber来实现迭代器,我们可以将循环的迭代器看作生产者-消费者模式的特殊的例子。迭代函数产生值给循环体消费。所以可以使用Fiber来实现迭代器。协程的一个关键特征是它可以不断颠倒调用者与被调用者之间的关系,这样我们毫无顾虑的使用它实现一个迭代器,而不用保存迭代函数返回的状态,也就是说无需在迭代函数中保存状态,状态的保存和恢复交由Fiber自动管理。

     这一节的介绍以一个例子贯穿前后,我们将不断演化这个例子,直到得到一个比较优雅的可重用的代码,这个例子就是求数组的全排列。如数组[1,2,3]的全排列包括6种排列:
2 3 1
3 2 1
3 1 2
1 3 2
2 1 3
1 2 3
全排列的递归算法实现很简单,我们用Ruby实现如下:

#全排列的递归实现
def permgen (a, n)
    
if n == 0 then
       printResult(a)
    
else
        n.times do 
|i|
           a[n
-1], a[i] = a[i], a[n-1]
           permgen(a, n 
- 1)
           a[n
-1], a[i] = a[i], a[n-1]
       end
    end
end

def printResult (a)
    puts a.join(
" ")
end

permgen([
1,2,3,4],4)
算法的思路是这样:
将数组中的每一个元素放到最后,依次递归生成所有剩余元素的排列,没完成一个排列就打印出来。很显然,这里有消费者和生产者的关系存在,生产者负责产生排列,消费者负责打印任务,整个程序由消费者驱动,因此用Fiber改写如下:

第一步,将打印任务修改为Fiber#yield,生产者产生一个排列后将结果传递给消费者并让出执行权:
def permgen (a, n)
    
if n == 0 then
       Fiber.
yield(a)
    ……
end

第二步,实现一个迭代器工厂,返回一个匿名的迭代函数,迭代函数请求Fiber产生一个新的排列:
def perm(a)
  f
=Fiber.new do
        permgen(a,a.size)
    end
  
return lambda{ f.resume if f.alive? }
end

这样一来我们就可以利用一个while循环来打印全排列:
it=perm([1,2,3,4])

while a=it.call
   printResult(a)
end

    注意到,在perm方法中有一个很常见的模式,就是将对Fiber的resume封装在一个匿名函数内,在lua为了支持这种模式还特意提供了一个coroutine.wrap方法来方便编程,在Ruby Fiber中却没有,不过我们可以自己实现下,利用open-class的特性实现起来非常简单:
#为Fiber添加wrap方法
class Fiber
  
def self.wrap
    
if block_given?
      f
=Fiber.new do |*args|
         
yield *args
      end
      
return lambda{|*args| f.resume(*args) if f.alive? }
    end
  end
end
 
    Fiber#wrap方法跟new方法一样,创建一个新的Fiber,但是返回的是一个匿名函数,这个匿名函数负责去调用fiber的resume,利用wrap改写perm方法变得更简洁:
def perm(a)
  Fiber.wrap{ permgen(a,a.size) }
end

    但是还不够,while循环的方式还是不够优雅,每次都需要明确地调用迭代器的call方法,这一点让人挺不舒坦,如果能像for...in那样的泛型循环就好了,我们知道Ruby中的for...in其实是一个语法糖衣,都是转变成调用集合的each方法并传入处理的block,因此,要想实现一个优雅的迭代器,我们做下封装就好了:

class FiberIterator
  
def initialize
    @fiber_wrap
=Fiber.wrap do
        
yield
    end
  end
  
def each
    
while value=@fiber_wrap.call
      
yield value
    end
  end
end

那么现在的perm方法变成了创建一个迭代器FiberIterator:
def perm(a)
  FiberIterator.new{ permgen(a,a.size) }
end
这样一来我们就可以通过for...in来调用迭代器了

it=perm([1,2,3,4])
for a in it
  printResult(a)
end


   

    

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


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

     在学习了Fiber的基础知识之后,可以尝试用Fiber去做一些比较有趣的事情。这一节将讲述如何使用Fiber来实现类似unix系统中的管道功能。在unix系统中,可以通过管道将多个命令组合起来做一些强大的功能,最常用的例如查找所有的java进程:
ps aux|grep java
通过组合ps和grep命令来实现,ps的输出作为grep的输入,如果有更多的命令就形成了一条过滤链。过滤器本质上还是生产者和消费者模型,前一个过滤器产生结果,后一个过滤器消费这个结果并产生新的结果给下一个过滤器消费。因此我们就从最简单的生产者消费者模型实现说起。
我们要展示的这个例子场景是这样:生产者从标准输入读入用户输入并发送给消费者,消费者打印这个输入,整个程序是由消费者驱动的,消费者唤醒生存者去读用户输入,生产者读到输入后让出执行权给消费者去打印,整个过程通过生产者和消费者的协作完成。
生产者发送是通过yield返回用户输入给消费者(还记的上一节吗?):

def send(x)
  Fiber.
yield(x)
end

而消费者的接收则是通过唤醒生产者去生产:
def receive(prod)
  prod.resume
end

生产者是一个Fiber,它的任务就是等待用户输入并发送结果给消费者:
def producer()
  Fiber.new do
    
while true
      x
=readline.chomp
      send(x)
    end
  end
end

消费者负责驱动生产者,并且在接收到结果的时候打印,消费者是root fiber:
def consumer(producer)
  
while true
    x
=receive(producer)
    
break if x=='quit'
    puts x
  end
end

    最终的调用如下:
consumer(producer())
   完整的程序如下:

#生产者消费者
require 'fiber'

def send(x)
  Fiber.
yield(x)
end

def receive(prod)
  prod.resume
end

def producer()
  Fiber.new do
    
while true
      x
=readline.chomp
      send(x)
    end
  end
end


def consumer(producer)
  
while true
    x
=receive(producer)
    
break if x=='quit'
    puts x
  end
end
if $0==__FILE__
  consumer(producer())
end

   读者可以尝试在ruby1.9下运行这个程序,每次程序都由消费者驱动生产者去等待用户输入,用户输入任何东西之后回车,生产者开始运行并将读到的结果发送给消费者并让出执行权(通过yield),消费者在接收到yield返回的结果后打印这个结果,因此整个交互过程是一个echo的例子。

最终的调用consumer(producer())已经有过滤器的影子在了,如果我们希望在producer和consumer之间插入其他过程对用户的输入做处理,也就是安插过滤器,那么新的过滤器也将作为fiber存在,新的fiber消费producer的输出,并输出新的结果给消费者,例如我们希望将用户的输入结果加上行号再打印,那么就插入一个称为filter的fiber:
def filter(prod)
  
return Fiber.new do
    line
=1
    
while true
      value
=receive(prod)
      value
=sprintf("%5d %s",line,value)
      send(value)
      line
=line.succ
    end
  end
end

    最终组合的调用如下:
 consumer(filter(producer()))
   类似unix系统那样,简单的加入新的fiber组合起来就可以为打印结果添加行号。

类似consumer(filter(producer()))的调用方式尽管已经很直观,但是我们还是希望能像unix系统那样调用,也就是通过竖线作为管道操作符:
producer | filter | consumer
这样的调用方式更将透明直观,清楚地表明整个过滤器链的运行过程。幸运的是在Ruby中支持对|方法符的重载,因此要实现这样的操作符并非难事,只要对Fiber做一层封装即可,下面给出的代码来自《Programming ruby》的作者Dave Thomas的blog

class PipelineElement
   attr_accessor :source
   
def initialize
      @fiber_delegate 
= Fiber.new do
         process
      end
   end

   
def |(other)
      other.source 
= self
      other
   end

   
def resume
      @fiber_delegate.resume
   end

   
def process
      
while value = input
         handle_value(value)
      end
   end

   
def handle_value(value)
      output(value)
   end

   
def input
      @source.resume
   end

   
def output(value)
      Fiber.
yield(value)
   end
end

这段代码非常巧妙,将Fiber和Ruby的功能展示的淋漓尽致。大致解说下,PipelineElement作为任何一个过滤器的父类,其中封装了一个fiber,这个fiber默认执行process,在process方法中可以看到上面生产者和消费者例子的影子,input类似receive方法调用前置过滤器(source),output则将本过滤器处理的结果作为参数传递给yield并让出执行权,让这个过滤器的调用者(也就是后续过滤器)得到结果并继续处理。PipelineElement实现了“|”方法,用于组合过滤器,将下一个过滤器的前置过滤器设置为本过滤器,并返回下一个过滤器。整个过滤链的驱动者是最后一个过滤器。

有了这个封装,那么上面生产者消费者的例子可以改写为:
class Producer < PipelineElement
   
def process
     
while true
       value
=readline.chomp
       handle_value(value)
     end
   end
end

class Filter < PipelineElement
  
def initialize
    @line
=1
    super()
  end
  
def handle_value(value)
    value
=sprintf("%5d %s",@line,value)
    output(value)
    @line
=@line.succ
  end
end

class Consumer < PipelineElement
  
def handle_value(value)
    puts value
  end
end

   现在的调用方式可以跟unix的管道很像了:
producer=Producer.new
filter
=Filter.new
consumer
=Consumer.new

pipeline 
= producer | filter | consumer
pipeline.resume
如果你打印pipeline对象,你将看到一条清晰的过滤链,
#<Consumer:0x8f08bf4 @fiber_delegate=#<Fiber:0x8f08a88>, @source=#<Filter:0x8f08db4 @line=1, @fiber_delegate=#<Fiber:0x8f08d60>, @source=#<Producer:0x8f09054 @fiber_delegate=#<Fiber:0x8f09038>>>>


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


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

    这一篇其实也算是Fiber编程的基础篇,只不过参数传递算是一个比较重要的主题,因此独立一节。参数传递发生在两个Fiber之间,作为Fiber之间通讯的一个主要手段。

    首先,我们可以通过resume调用给Fiber的block传递参数:
1 #resume传递参数给fiber
2 f=Fiber.new do |a,b,c|
3    p a,b,c
4 end
5 
6 f.resume(1,2,3)
7 

这个例子展示了怎么向fiber的block传递参数,f这个fiber简单地将传入的参数打印出来并终止。

    其次,Fiber#yield也可以传递参数给调用resume作为返回结果,猜猜下面的代码打印什么?

1 #yield传递参数给resume
2 f=Fiber.new do |a,b|
3   Fiber.yield a+b,a-b
    p a,b
4 end
5 
6 p f.resume(10,10)
7 p f.resume(3,4)
8 
正确的答案是:
[20, 0]
10
10
[
1010]
让我们分析下代码的执行过程:
1、第6行第一次调用resume,传入10,10两个参数
2、f开始执行任务,它的任务是调用Fiber#yield,并将参数相加和相减的结果作为参数给yield,也就是执行Fiber.yield 20,10
3、f调用yield之后挂起,返回root fiber,yield的两个参数10、20作为返回结果打印。
4、第7行代码,root fiber再次调用resume并传入参数,f被切入并执行代码p a,b,打印a、b,a和b仍然是上次调用保存的10,而非resume传入的3和4。
5、f执行完毕,返回p a,b的结果作为resume结果,也就是[10,10]

    刚才看到上面yield向resume传递参数的例子中第二次调用resume的参数3和4被忽略了,事实上如果还存在一次yield调用,那么3和4将被作为yield的返回结果使用,这就是我们接下来将看到的,通过resume调用传递参数作为fiber中yield的返回结果:

1 #resume传递参数给yield
2 f=Fiber.new do
3    1 + Fiber.yield
4 end
5 
6 p f.resume(1)
7 p f.resume(2)
8 

这次的打印结果将是:

nil
3
   第一次调用resume传入的1将被忽略,因为f的block不需要参数,然后f执行1 + Fiber.yield,在yield的挂起,加法运算没有继续,因为yield的调用没有参数,因此第一次resume返回nil;第二次resume调用传入2,这时候2将作为Fiber#yield的调用结果跟1相加,完成加法运算,得到的结果就是3,这个结果作为fiber的返回值返回给调用者。

    总结下上面我们谈到的四种传递参数的情形:通过resume向fiber的block传递参数、通过yield向调用者传递参数、通过resume向yield传递参数、fiber返回值传递给调用者。
   

posted @ 2010-03-11 18:41 dennis 阅读(3713) | 评论 (0)编辑 收藏


    Ruby Fiber指南(一)基础
    Ruby Fiber指南(二)参数传递
    Ruby Fiber指南(三)过滤器
    Ruby Fiber指南(四)迭代器
    Ruby Actor指南(五)实现Actor
   
    这是一个Ruby Fiber的教程,基本是按照《Programming in lua》中讲述协程章节的顺序来介绍Ruby Fiber的,初步分为5节:基础、参数传递、过滤器、迭代器、应用。这是第一节,介绍下Ruby Fiber的基础知识。

    Ruby 1.9引入了Fiber,通常称为纤程,事实上跟传统的coroutine——协程是一个概念,一种非抢占式的多线程模型。所谓非抢占式就是当一个协程运行的时候,你不能在外部终止它,而只能等待这个协程主动(一般是yield)让出执行权给其他协程,通过协作来达到多任务并发的目的。协程的优点在于由于全部都是用户空间内的操作,因此它是非常轻量级的,占用的资源很小,并且context的切换效率也非常高效(可以看看这个测试),在编程模型上能简化对阻塞操作或者异步调用的使用,使得涉及到此类操作的代码变的非常直观和优雅;缺点在于容错和健壮性上需要做更多工作,如果某个协程阻塞了,可能导致整个系统挂住,无法充分利用多核优势,有一定的学习使用曲线。
   上面都是场面话,先看看代码怎么写吧,比如我们写一个打印hello的协程:
 1 require 'fiber'
 2 f=Fiber.new do
 3   p "hello"
 4 end
 5 
 6 p f.alive?
 7 f.resume
 8 p f.alive?
 9 
10 f.resume
11
    附注:这里的代码都在ruby1.9.1-p378测试通过。

     第一行先引入fiber库,事实上fiber库并不是必须的,这里是为了调用Fiber#alive?方法才引入。然后通过Fiber#new创建一个Fiber,Fiber#new接受一个block,block里就是这个Fiber将要执行的任务。Fiber#alive?用来判断Fiber是否存活,一个Fiber有三种状态:Created、Running、Terminated,分别表示创建完成、执行、终止,处于Created或者Running状态的时候Fiber#alive?都返回true。启动Fiber是通过Fiber#resume方法,这个Fiber将进入Running状态,打印"hello"并终止。当一个Fiber终止后,如果你再次调用resume将抛出异常,告诉你这个Fiber已经寿终正寝了。因此上面的程序输出是:
0
"hello"
false
fiber1.rb:
10:in `resume': dead fiber called (FiberError)
    from fiber1.rb:10:in `<main>'

     眼尖的已经注意到了,这里alive?返回是0,而不是true,这是1.9.1这个版本的一个BUG,1.9.2返回的就是true。不过在Ruby里,除了nil和false,其他都是true。

    刚才提到,我们为了调用Fiber#alive?而引入了fiber库,Fiber其实是内置于语言的,并不需要引入额外的库,fiber库对Fiber的功能做了增强,具体可以先看看它的文档,主要是引入了几个方法:Fiber#current返回当前协程,Fiber#alive?判断Fiber是否存活,最重要的是Fiber#transfer方法,这个方法使得Ruby的Fiber支持所谓全对称协程(symmetric coroutines),默认的resume/yield(yield后面会看到)是半对称的协程(asymmetric coroutines),这两种模型的区别在于挂起一个正在执行的协同函数”与“使一个被挂起的协同再次执行的函数”是不是同一个。在这里就是Fiber#transfer一个方法做了resume/yield两个方法所做的事情。全对称协程就可以从一个协程切换到任意其他协程,而半对称则要通过调用者来中转。但是Ruby Fiber的调用不能跨线程(thread,注意跟fiber区分),只能在同一个thread内进行切换,看下面代码:
1 = nil
2 Thread.new do
3   f = Fiber.new{}
4 end.join
5 f.resume

f在线程内创建,在线程外调用,这样的调用在Ruby 1.9里是不允许的,执行的结果将抛出异常
fiber_thread.rb:5:in `resume': fiber called across threads (FiberError)
    from fiber_thread.rb:5:in `<main>'

    刚才我们仅仅使用了resume,那么yield是干什么的呢?resume是使一个挂起的协程执行,那么yield就是让一个正在执行的Fiber挂起并将执行权交给它的调用者,yield只能在某个Fiber任务内调用,不能在root Fiber调用,程序的主进程就是一个root fiber,如果你在root fiber执行一个Fiber.yield,也将抛出异常:
 Fiber.yield
FiberError: can
't yield from root fiber
  
    看一个resume结合yield的例子:
 1 f=Fiber.new do
 2   p 1
 3   Fiber.yield
 4   p 2
 5   Fiber.yield
 6   p 3
 7 end
 8 
 9 f.resume # =>打印1
10 f.resume # => 打印2
11 f.resume # =>打印3

   f是一个Fiber,它的任务就是打印1,2,3,第一次调用resume时,f在打印1之后调用了Fiber.yield,f将让出执行权给它的调用者(这里就是root fiber)并挂起,然后root fiber再次调用f.resume,那么将从上次挂起的地方继续执行——打印2,又调用Fiber.yield再次挂起,最后一次f.resume执行后续的打印任务并终止f。

    Fiber#yield跟语言中的yield关键字是不同的,block中的yield也有“让出”的意思,但是这是在同一个context里,而Fiber#yield让出就切换到另一个context去了,这是完全不同的。block的yield其实是匿名函数的语法糖衣,它是切换context的,跟Fiber不同的是,它不保留上一次调用的context,这个可以通过一个例子来区分:
1 def test
2    yield
3    yield
4    yield
5 end
6 test{x ||= 0; puts x+= 1}
7 
这里的test方法接受一个block,三次调用yield让block执行,block里先是初始化x=0,然后每次调用加1,你期望打印什么?
答案是:
1
1
1
这个结果刚好证明了yield是不保留上一次调用的context,每次x都是重新初始化为0并加上1,因此打印的都是1。让我们使用Fiber写同一个例子:
 1 fiber=Fiber.new do
 2    x||=0
 3    puts x+=1
 4    Fiber.yield
 5    puts x+=1
 6    Fiber.yield
 7    puts x+=1
 8    Fiber.yield
 9 end
10 
11 fiber.resume
12 fiber.resume
13 fiber.resume
14 
执行的结果是:
1
2
3
这次能符合预期地打印1,2,3,说明Fiber的每次挂起都将当前的context保存起来,留待下次resume的时候恢复执行。因此关键字yield是无法实现Fiber的,fiber其实跟continuation相关,在底层fiber跟callcc的实现是一致的(cont.c)。

    Fiber#current返回当前执行的fiber,如果你在root fiber中调用Fiber.current返回的就是当前的root fiber,一个小例子:
1 require 'fiber'
2 f=Fiber.new do
3    p Fiber.current
4 end
5 
6 p Fiber.current
7 f.resume

这是一次输出:
#<Fiber:0x9bf89f4>
#
<Fiber:0x9bf8a2c>
表明root fiber跟f是两个不同的Fiber。
    
     基础的东西基本讲完了,最后看看Fiber#transfer的简单例子,两个协程协作来打印“hello world”:
 1 require 'fiber'
 2 
 3 f1=Fiber.new do |other|
 4     print "hello"
 5     other.transfer
 6 end
 7 
 8 f2=Fiber.new do
 9     print " world\n"
10 end
11 
12 f1.resume(f2)

通过这个例子还可以学到一点,resume可以传递参数,参数将作为Fiber的block的参数,参数传递将是下一节的主题。


   

posted @ 2010-03-11 12:53 dennis 阅读(8629) | 评论 (2)编辑 收藏

最近重读了《Programming Lua》,对协程做了重点复习。众所周知,Ruby1.9引入了Fiber,同样是coroutine,不过Ruby Fiber支持全对称协程(通过fiber库),而Lua只支持所谓半对称协程。

    这里将对Lua、LuaJIT和Ruby Fiber的切换效率做个对比测试,测试场景很简单:两个coroutine相互切换达到5000万次,统计每秒切换的次数,各测试多次取最佳。

    lua的程序如下:
   
c1=coroutine.create(function ()
                     
while true do
                       coroutine
.yield()
                     end
                    end)

c2
=coroutine.create(function ()
                     
while true do
                       coroutine
.yield()
                     end
                    end)

local start=os.clock()
local count=50000000

for i=1,count do
 coroutine
.resume(c1)
 coroutine
.resume(c2)
end

print(4*count/(os.clock()-start))

    考虑到在循环中事实上发生了四次切换:main->c1,c1->main,main->c2,c2->main,因此乘以4。

    Ruby Fiber的测试分两种,采用transfer的例程如下:

require 'fiber'
require 'benchmark'

Benchmark
.bm do |x|
  MAX_COUNT
=50000000
  f1
=Fiber.new do |other,count|
     
while count<MAX_COUNT
      other
,count=other.transfer(Fiber.current,count.succ)
     end
  end

  f2
=Fiber.new do |other,count|
    
while count<MAX_COUNT
      other
,count=other.transfer(Fiber.current,count.succ)
    end
  end

  x
.report{
    f1
.resume(f2,0)
  }
end
     Ruby Fiber采用resume/yield的例程如下:
require 'benchmark'
f1
=Fiber.new do
  
while true
    Fiber
.yield
  end
end
f2
=Fiber.new do
  
while true
    Fiber
.yield
  end
end

COUNT
=50000000

Benchmark
.bm do |x|
  x
.report{
     COUNT
.times do
         f1
.resume
         f2
.resume
     end
  }
end



     测试环境:
          CPU :    Intel(R) Core(TM)2 Duo CPU     P8600  @ 2.40GHz
          Memory:  3GB
          System :  Linux dennis-laptop 2.6.31-14-generic #48-Ubuntu SMP
          Lua    : 5.1.4
          ruby  :  1.9.1p378
          LuaJIT:  1.1.5和2.0.0-beta2

      测试结果如下:
    
  Lua LuaJIT 1.1.5
 LuaJIT 2.0.0-beta2
 ruby-transfer
 ruby-resume/yield
 次数 6123698 9354536 24875620 825491 969649


      结论:
      1、lua的协程切换效率都是百万级别,luaJIT 2.0的性能更是牛叉,切换效率是原生lua的4倍,达到千万级别。
      2、相形之下,Ruby Fiber的效率比较差了,十万级别。
      3、Ruby使用transfer的效率比之resume/yield略差那么一点,排除一些测试误差,两者应该是差不多的,从ruby源码上看resume/yield和transfer的调用是一样的,resume还多了几条指令。
      4、额外信息,Ruby Fiber和lua coroutine都只能跑在一个cpu上,这个测试肯定能跑满一个cpu,内存占用上,lua也比ruby小很多。
 

posted @ 2010-03-02 11:50 dennis 阅读(7405) | 评论 (2)编辑 收藏

    题外:从老家从早到晚总算折腾回了杭州,进站太早,火车晚点,提包带断,什么倒霉事也遇上了,先发个已经整理好的部分,后续仍待整理。

多道程序设计:分离进程为独立的功能

无论在协作进程还是在同一进程的协作子过程层面上,Unix设计风格都运用“做单件事并做好的方法“,强调用定义良好的进程间通信或共享文件来连通小型进程,提倡将程序分解为更简单的子进程,并专注考虑这些子进程间的接口,这至少需要通过以下三种方法来实现:

1降低进程生成的开销(思考下Erlangprocess)

2、提供方法(shelloutIO重定向、管道、消息传递和socket)简化进程间通信

3、提倡使用能由管道和socket传递的简单、透明的文本数据格式

Unix IPC方法的分类:

1、将任务转给专门程序,如system(3)popen调用等,称为shellout

2Pipe重定向和过滤器,如bcdc

3包装器,隐藏shell管线的复杂细节。

4安全包装器和Bernstein

5/进程

6对等进程间通信:

(1)临时文件

(2)信号

(3)系统守护程序和常规信号

(4)socket

(5)共享内存,mmap

远程过程调用(RPC)的缺憾:

1RPC接口很难做到可显,如果不编写和被监控程序同样复杂的专用工具,也难以监控程序的行为。RPC接口和库一样具有版本不兼容的问题,由于是分布式的,因此更难被追查。

2、类型标记越丰富的接口往往越复杂,因而越脆弱。随着时间的推移,由于在接口之间传递的类型总量逐渐变大,单个类型越来越复杂,这些接口往往产生类型本体蠕变问题。这是因为接口比字符串更容易失配;如果两端程序的本体不能正确匹配,要让它们通信肯定很难,纠错更是难上加难。

3、支持RPC的常见理由是它比文本流方法允许“更丰富”的接口,但是接口的功能之一就是充当阻隔点,防止模块的实现细节彼此泄漏,因此,支持RPC的主要理由同时恰恰证明了RPC增加而不是降低了程序的全局复杂度

Unix传统强烈赞成透明、可显的接口,这是unix文化不断坚持文本协议IPC的动力。

ESR在这里还谈到XML-RPCSOAP等协议,认为是RPCunix对文本流支持的一种融合,遗憾的是SOAP本身也成为一种重量级、不那么透明的协议了,尽管它也是文本协议。

线程是有害的:

线程是那些进程生成昂贵、IPC功能薄弱的操作系统所特有的概念。

尽管线程通常具有独立的局部变量栈,它们却共享同一个全局内存,在这个共享地址空间管理竞争临界区的任务相当困难,而且成为增加整体复杂度和滋生bug的温床。除了普通的竞争问题之外,还产生了一类新问题:时序依赖

当工具的作用不是控制而是增加复杂度的时候,最好扔掉从零开始。

微型语言:寻找歌唱的乐符

(注,这里谈的微型语言,就是现在比较热门的词汇DSL

对软件错误模式进行的大量研究得出的一个最一致的结论是,程序员每百行程序出错率和所使用的编程语言在很大程度上是无关的。更高级的语言可以用更少的行数完成更多的任务,也意味着更少的bug

防御设计不良微型语言的唯一方法是知道如何设计一个好的微型语言。

语言分类法:


对微型语言的功能测试:不读手册可以编写吗

现代微型语言,要么就完全通用而不紧凑,要么就非常不通用而紧凑;不通用也不紧凑的语言则完全没有竞争力。

一些引申想法:我认为这个评判标准也可以用在任何编程语言上,以此来判断一些语言,C语言既通用又紧凑,Java是通用而不紧凑,rubyPython之类的脚本语言也是如此,正则表达式(如果也算语言的话)是不通用而紧凑,Erlang也是通用而紧凑,awk却是既不通用也不紧凑,XSLT也可以归入不通用不紧凑的行列;Javascript是个另类,按理说它也是不通用不紧凑,说它不通用是因为它的主要应用范围还是局限在web开发的UI上,实际上Javascript也是门通用语言,但是很少会有人会用javascript去写批处理脚本,Javascript显然是不紧凑的,太多的边边角角甚至奇奇怪怪的东西需要你去注意,然而就是这样一门不通用不紧凑的语言现在却非常有前途,只能说时势所然。

设计微型语言:

1、选择正确的复杂度,要的是数据文件格式,还是微型语言?

2扩展嵌入语言

3、编写自定义语法yacclex

4慎用宏,宏的主要问题是滥用带来的奇怪、不透明的代码,以及对错误诊断的扰乱。

5语言还是应用协议



posted @ 2010-02-22 00:46 dennis 阅读(2978) | 评论 (1)编辑 收藏

模块性:保持清晰,保持简洁

软件设计有两种方式:一种是设计得极为简洁没有看得到的缺陷;另一种是设计得极为复杂有缺陷也看不出来。第一种方式的难度要大得多。

模块化代码的首要特质就是封装API在模块间扮演双重角色,实现层面作为模块之间的滞塞点阻止各自的内部细节被相邻模块知晓;在设计层面,正是API真正定义了整个体系。

养成在编码前为API编写一段非正式书面描述的习惯,是一个非常好的方法。

模块的最佳大小,逻辑行200-400行,物理行在400-800之间

紧凑性就是一个设计能否装入人脑中的特性。测试软件紧凑性的一个简单方法是:一个有经验的用户通常需要用户手册吗?如果不需要,那么这个设计是紧凑的。

理解紧凑性可以从它的“反面”来理解,紧凑性不等于“薄弱”,如果一个设计构建在易于理解且利于组合的抽象概念上,则 这个系统能在具有非常强大、灵活的功能的同时保持紧凑。紧凑也不等同于“容易学习”:对于某些紧凑 设计而言,在掌握其精妙的内在基础概念模型之前,要理解这个设计相当困难;但一旦理解了这个概念模型,整个视角就会改变,紧凑的奥妙也就十分简单了。紧凑也不意味着“小巧”。即使一个设计良好的系统,对有经验的用户来说没什么特异之处、“一眼”就能看懂,但仍然可能包含很多部分。

评测一个API紧凑性的经验法则是:API入口点通常在7左右,或者按《代码大全2》的说法,7+27-2的范围内。

重构技术中的很多坏味道,特别是重复代码,是违反正交性的明显例子,“重构的原则性目标就是提高正交性”。

DRY原则,或者称为SPOT原则(single point of truth)——真理的单点性。重复的不仅仅是代码,还包括数据结构,数据结构模型应该最小化,提倡寻找一种数据结构,使得模型中的状态跟真实世界系统的状态能够一一对应

要提高设计的紧凑性,有一个精妙但强大的方法,就是围绕“解决一个定义明确的问题”的强核心算法组织设计,避免臆断和捏造,将任务的核心形式化,建立明确的模型。


文本化:好协议产生好实践

文本流是非常有用的通用格式,无需专门工具就可以很容易地读写和编辑文本流,这些格式是透明的。如果担心性能问题,就在协议层面之上或之下压缩文本协议流,最终产生的设计会比二进制协议更干净,性能可能更好。使用二进制协议的唯一正当理由是:如果要处理大批量的数据,因而确实关注能否在介质上获得最大位密度,或者是非常关心将数据转化为芯片核心结构所必须的时间或指令开销

数据文件元格式:

1DSV风格DElimiter-Seperated Values

使用分隔符来分隔值,例如/etc/passwd

适合场景:数据为列表,名称(首个字段)为关键字,而且记录通常很短(小于80个字符)

2RFC 822格式

互联网电子邮件信息采用的文本格式,使用属性名+冒号+值的形式,记录属性每行存放一个,如HTTP 1.1协议。

适合场景:任何带属性的或者与电子邮件类似的信息,非常适合具有不同字段集合而字段中数据层次又扁平的记录。

3Cookie-jar格式。简单使用跟随%%的新行符(或者有时只有一个%)作为记录分隔符,很适用于记录非结构化文本的情况。

适合场景:词以上结构没有自然顺序,而且结构不易区别的文本段,或适用于搜索关键字而不是文本上下文的文本段。

4Record-jar格式cookie-jarRFC-822的结合,形如

name:dennis
age:
21
%%
name:catty
age:
22
%%
name:green
age:
10

这样的格式。

适合场景:那些类似DSV文件,但又有可变字段数据而且可能伴随无结构文本的字段属性关系集合。

5XML格式,适合复杂递归和嵌套数据结构的格式,并且经常可以在无需知道数据语义的情况下仅通过语法检查就能发现形式不良损坏或错误生成的数据。缺点在于无法跟传统unix工具协作。

6Windows INI格式,形如

[DEFAULT]
account
=esr

[python]
directory
=/home/ers/cvs/python
developer
=1

[sng]
directory
=/home/esr/WWW/sng
numeric_id
=1001
developer
=1

[fetchmail]
numeric_id
=4928492

这样的格式

适合场景:数据围绕指定的记录或部分能够自然分成“名称-属性对”两层组织结构。

Unix文本文件格式的约定:

1、如果可能,以新行符结束的每一行只存一个记录

2、如果可能,每行不超过80字符

3、使用”“引入注释

4、支持反斜杠约定

5、在每行一条记录的格式中,使用冒号或连续的空白作为字段分隔符。

6、不要过分区分tabwhitespace

7、优先使用十六进制而不是八进制。

8、对于复杂的记录,使用“节(stanza)”格式,要么让记录格式和RFC 822电子邮件头类似,用冒号终止的字段名关键字作为引导字段。

9、在节格式中,支持连续行,多个逻辑行折叠成一个物理行

10、要么包含一个版本号,要么将格式设计成相互独立的的自描述字节块。

11、注意浮点数取整。

12不要仅对文件的一部分进行压缩或者二进制编码。

应用协议元格式

1、经典的互联网应用元协议 RFC 3117《论应用协议的设计》,如SMTPPOP3IMAP等协议

2、作为通用应用协议的HTTP,在HTTP上构建专用协议,如互联网打印协议(IPP

3BEEP:块可扩展交换协议,既支持C/S模式,又支持P2P模式

4XMP-RPCSOAPJabber,基于XML的协议。


透明性:来点光

美在计算科学中的地位,要比在其他任何技术中的地位都重要,因为软件是太复杂了。美是抵御复杂的终极武器

如果没有阴暗的角落和隐藏的深度,软件系统就是透明的。透明性是一种被动品质。如果实际上能预测到程序行为的全部或大部分情况,并能建立简单的心理模型,这个程序就是透明的,因为可以看透机器究竟在干什么。

如果软件系统所包含的功能是为了帮助人们对软件建立正确的“做什么、怎样做”的心理模型而设计,这个软件系统就是可显的。

不要让调试工具仅仅成为一种事后追加或者用过就束之高阁的东西。它们是通往代码的窗口:不要只在墙上凿出粗糙的洞,要修整这些洞并装上窗。如果打算让代码一直可被维护,就始终必须让光照进去。例如fetchmail-v选项将处理SMTPPOP的处理过程打印到标准输出,使得fetchmail行为具有可显性。

在“这个设计能行吗?”之后要提出的头几个问题就是“别人能读懂这个设计吗?这个设计优雅吗?”我们希望,此时大家已经很清楚,这些问题不是废话,优雅不是一种奢侈。在人类对软件的反映中,这些品质对于减少软件bug和提高软件长期维护性是最基本的。

要追求代码的透明性,最有效的方法是很简单,就是不要在具体操作的代码上叠放太多的抽象层。

OO语言使得抽象变得容易——也许是太容易了。OO语言鼓励“具有厚重的胶合和复杂层次”的体系。当问题领域真的很复杂,确实需要大量抽象时,这可能是好事,但如果coder到头来用复杂的办法做简单的事情——仅仅是为他们能够这样做,结果便适得其反。

所有的OO语言都显示出某种使程序员陷入过度分层陷阱的倾向。对象框架和对象浏览器并不能代替良好的设计和文档,但却常常被混为一谈。过多的层次破坏了透明性:我们很难看清这些层次,无法在头脑中理清代码到底是怎样运行的。简洁、清晰和透明原则通通被破坏了,结果代码中充满了晦涩的bug,始终存在维护问题。

胶合层中的“智能数据”却经常不代表任何程序处理的自然实体——仅仅只是胶合物而已(典型现象就是抽象类和混入(mixin)的不断扩散)

OO抽象的另一个副作用就是程序往往丧失了优化的机会。

OO在其取得成功的领域(GUI、仿真和图形)之所以能成功,主要原因之一可能是因为在这些领域里很难弄错类型的本体问题。例如,在GUI和图形系统中,类和可操作的可见对象之间有相当自然的映射关系

Unix风格程序设计所面临的主要挑战就是如何将分离法的优点(将问题从原始的场景中简化、归纳)同代码和设计的薄胶合、浅平透层次结构的优点相组合。

太多的OO设计就像是意大利空心粉一样,把“is-a”have a的关系弄得一团糟,或者以厚胶合层为特征,在这个胶合层中,许多对象的存在似乎只不过是为了在陡峭的抽象金字塔上占个位置罢了。这些设计都不透明,它们晦涩难懂并且难以调试。

为透明性和可显性而编码:

1、程序调用层次中(不考虑递归)最大的静态深度是多少?如果大于,就要当心。

2、代码是否具有强大、明显的不变性质(约束)?不变性质帮助人们推演代码和发现有问题的情况。

3、每个API中各个函数调用是否正交?或者是否存在太多的magic flags或者模式位?

4、是否存在一些顺手可用的关键数据结构或全局唯一的记录器,捕获系统的高层次状态?这个状态是否容易被形象化和检验,还是分布在数目众多的各个全局变量或对象中而难以找到?

5、程序的数据结构或分类和它们所代表的外部实体之间,是否存在清晰的一对一映射。

6、是否容易找到给定函数的代码部分?不仅单个函数、模块,还有整个代码,需要花多少精力才能读懂

7、代码增加了特殊情况还是避免了特殊情况?每一个特殊情况可能对任何其他特殊情况产生影响:所有隐含的冲突都是bug滋生的温床。然而更重要的是,特殊情况使得代码更难理解。

8、代码中有多少个magic number?通过审查是否很容易查出代码中的限制(比如关键缓冲区的大小)?

隐藏细节无法访问细节有着重要区别。不要过度保护

无论何时碰到涉及编辑某类复杂二进制对象的设计问题时,unix传统都提倡首先考虑,是否能够编写一个能够在可编辑的文本格式和二进制格式之间来回进行无损转换的工具?这类工具可称为文本化器(textualizer).

宁愿抛弃、重建代码也不愿修补那些蹩脚的代码。

“代码是活代码、睡代码还是死代码?”活代码周围存在一个非常活跃的开发社团。睡代码之所以“睡着”,经常是因为对作者而言,维护代码的痛苦超过了代码本身的效用。死代码则是睡得太久,重新实现一段等价代码更容易

posted @ 2010-02-19 19:25 dennis 阅读(3389) | 评论 (1)编辑 收藏

   Unix哲学是自下而上,而不是自上而下的,注重实效,立足于丰富的经验,你不会在正规方法学和标准中找到它。

   Unix管道的发明人Doug McIlroy曾经说过:

1、让每个程序就做好一件事,如果有新任务就重新开始,不要往新程序中加入功能而搞的复杂。

2、假定每个程序的输出都会成为另一个程序的输入,哪怕那个程序是未知的。输出中不要有无关的信息干扰,避免使用严格的分栏格式和二进制格式输入。不要坚持使用交互式输入。

3、尽可能早将设计和编译的软件投入试用,哪怕是操作系统也不例外,理想情况下应该是几星期内,对抽劣的代码别犹豫,扔掉重写

4、优先使用工具,而非拙劣的帮助来减轻编程任务的负担,工欲善其事,必先利其器。


Rob Pike在《Notes on C programming》中提到:

原则1:你无法断行程序会在什么地方耗费运行时间。瓶颈经常出现在想不到的地方,所以别急于胡乱找个地方改代码,除非你已经证实那儿就是瓶颈所在。

原则2:估量。在你没对代码进行估量,特别是没找到最耗时的那部分之前,别去优化速度。

原则3:花哨的算法,在n很小的适合通常很慢,而n通常很小。花哨算法的常数复杂度很大,除非你确定n一直很大,否则不要用花哨算法(即使n很大,也要优先考虑原则2)。

原则4:花哨的算法比简单的算法更容易出bug ,更难实现。尽量使用简单的算法配合简单的数据结构。

原则5数据压倒一切。如果已经选择了正确的数据结构并且把一切组织得井井有条,正确的算法也就不言自明,编程的核心是数据结构,而不是算法。

原则6:没有原则6.


Ken Thompson对原则4做了强调:

拿不准就穷举。


Unix哲学的17条原则:

1、模块原则:简洁的接口拼合简单的部件。

2、清晰原则:清晰胜于机巧。

3、组合原则:设计时考虑拼接组合。

4、分离原则:策略同机制分离,接口同引擎分离。

5、简洁原则:设计要简洁,复杂度能低则低。

6、吝啬原则:除非却无他法,不要编写庞大的程序。

7、透明性原则:设计要可见,以便审查和调试。

8、健壮原则:健壮源于透明与简洁。

9、表示原则:把知识叠入数据,以求逻辑质朴而健壮。

10、通俗原则:接口设计避免标新立异。

11、缄默原则:如果一个程序没什么好说的,就沉默。

12、补救原则:出现异常时,马上退出,并给出足够错误信息。

13、经济原则:宁花机器一分,不花程序员一秒。

14、生成原则:避免手工hack,尽量编写程序去生成程序。

15、优化原则:雕琢前先要有原型,跑之前先学会走。

16、多样原则:绝不相信所谓“不二法门”的断言。

17、扩展原则:设计着眼未来,未来总是比预想来得快。


Unix哲学之一言以蔽之:KISS

Keep it simple,stupid!

应用unix哲学:

1、只要可行,一切都应该做成与来源和目标无关的过滤器

2、数据流应尽可能的文本化(这样可以用标准工具来查看和过滤)。

3、数据库部署和应用协议应尽可能文本化(让人阅读和编辑)。

4、复杂的前端(用户界面)和后端应该泾渭分明

5、如果可能,用c编写前,先用解释性语言搭建原型

6、当且仅当只用一门编程语言会提高程序复杂度时,混用语言编程才比单一语言编程来得好。

7宽收严发(对接收的东西要包容,对输出的东西要严格)

8、过滤时,不需要丢弃的消息绝不丢。

9小就是美。在确保完成任务的基础上,程序功能尽可能的少。


最后强调的是态度:

要良好地运用unix哲学,你就应该不断地追求卓越,你必须相信,程序设计是一门技艺,值得你付出所有的智慧、创造力和激情。否则,你的视线就不会超越那些简单、老套的设计和实现;你就会在应该思考的时候急急忙忙跑去编程。你就会在该无情删繁就简的时候反而把问题复杂化——然后你还会反过来奇怪你的代码怎么会那么臃肿,那么难以调试。

要良好地运用unix哲学,你应该珍惜你的时间绝不浪费。一旦某人已经解决了某个问题,就直接拿来利用,不要让骄傲或偏见拽住你又去重做一遍。永远不要蛮干;要多用巧劲,省下力气在需要的时候用,好钢用到刀刃上。善用工具,尽可能将一切自动化

软件设计和实现是一门充满快乐的艺术,一种高水平的游戏。如果这种态度对你来说听起来有些荒谬,或者令你隐约感到有些困窘,那么请停下来,想一想,问问自己是不是已经把什么给遗忘了。如果只是为了赚钱或者打发时间,你为什么要搞软件设计,而不是别的什么呢?你肯定曾经也认为软件设计值得你付出激情……

要良好地运用unix哲学,你需要具备(或者找回)这种态度。你需要用心。你需要去游戏。你需要乐于探索。


操作系统的风格元素:

1、什么是操作系统的统一性理念

2、多任务能力

3、协作进程(IPC

4、内部边界

5、文件属性和记录结构

6、二进制文件格式

7、首选用户界面风格

8、目标受众

9、开发的门坎

posted @ 2010-02-18 19:23 dennis 阅读(7834) | 评论 (7)编辑 收藏

    经常在china-pub上买书,我的账号早已经到五星,再加上china-pub上很多新书首发,因此尽管当当网有时候更便宜,还是经常在china-pub买。不过这次我要出离愤怒了,同样是上个月29号下的单,当当在周一就送到了,而china-pub到今天5号竟然还没有送到,看订单信息是货已出库,并且发货时间在1月31号,从北京到杭州走了5天竟然还没到,选的什么快递公司。
    不到也还罢了,更可恶的是售后服务,我在china-pub的客服论坛发帖,他们自己承诺工作时间内60分钟回复,回复个P啊,从昨天到今天没见一个人点击我的帖子,更何谈回复。OK,论坛不行,那么我发邮件吧,从china-pub的客服服务页的客服email进去,填写表单,OK,提交失败?为什么,没有验证码,可是你TMD根本没显示验证码啊,这是什么狗屁程序,见下图



 你看到验证码在哪里吗?刷新N遍愣是没出来,多牛B的客服email啊。

      线上不行,那么我打电话可以吧,这电话是普通长途也还罢了,我自己掏钱没事,可总得有人接吧,事实是我早上打了3个电话,两个查询订单,等了N久没人接,靠,那我投诉吧,转投诉,一样没人接,我只能说china-pub你们真牛气,你们的客服是摆设不成?你们这么牛气,我也不敢买了,惹不起我还躲不起啊。




posted @ 2010-02-05 09:53 dennis 阅读(3104) | 评论 (16)编辑 收藏

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