More servicesWindows Live
HomeHotmailSpacesOneCare
 
MSN
Sign in
 
 
Spaces home  星の海——天锁斩月PhotosProfileFriendsMore Tools Explore the Spaces community

星の海——天锁斩月

大江歌罢掉头东,邃密群科济世穷;面壁十年图破壁,难酬蹈海亦英雄。
Updated 4/6/2008
Updated 1/27/2008
Updated 4/5/2007
Updated 11/7/2006
Updated 9/4/2006
Updated 5/7/2006
Updated 11/27/2005
Updated 10/14/2005
Updated 10/14/2005
Updated 10/14/2005
Updated 8/24/2007
July 03

Observer in Haskell

当我们贯彻数据和操作分离的原则时,常常需要实现这样的一种模式:一个数据源(subject),若干观察者(observer)。每当数据源发生变化的时候,观察者可以看到它的变化,并且作出相应的行为。在OOP的领域当中我们就称呼此为Observer模式。
经受OOP思维熏陶之后,我自然能很画出Observer模式的类图,清楚的表达subject和observer之间的相互关系。但是现在我需要在Haskell中表达这样的思想,如何办?对象不再是思考的核心所在,对象之间的消息传递才是关键。经过一段时间的思考,我作出基本设计。观察到我需要Observer模式的根本原因是我希望针对源的一个变化,有一组对象需要获得这个新的数据施加某些变换后的结果。我采用多线程的方式,观察者位于一个独立的线程,其功能就是每当数据源发生变动时,对新的数据施加一个变换函数,获得一个结果,保存起来,供今后的时候。而在数据源不变的时候,这个线程保持阻塞,保持上一次的结果。看似采用多线程似乎把问题做复杂了,但是通过STM(Software Transactional Memory)的编程接口,一切异常轻松。

-- MessageSlot是数据源与观察者直接共享的消息通道
type MessageSlot a = TChan a
-- SharedObject是观察者根据数据源,作出变换操作后存储的结果
type SharedObject a = TVar (Maybe a)
-- Subject 是 某个数据类型和其消息共享变量的偶对
type Subject a = (a, MessageSlot a)

-- 创建数据源,参数是数据源的初始值,将这个初始值和构造的消息变量做一个偶对
newSubject a = (,) a `fmap` atomically newTChan

-- 创建观察者线程,参数ms是消息共享通道,参数onChange是每当数据源变化时施以的变换操作
newObserver ms onChange = do{ so <- newTVarIO Nothing -- 创建一个结果共享变量
                            ; ch <- atomically $ dupTChan ms
                            ; forkIO $ notifier ch so -- 创建观察者线程
                            ; return so
                            }
-- 观察者线程从消息共享通道读取消息,施加变换操作,然后写入结果变量
    where notifier ch so = forever . atomically $ do{ a <- readTChan ch
                                                    ; writeTVar so (Just $ onChange a)
                                                    }
                                                   
-- 更新数据源操作,向消息共享通道写入新的数据
updateS (a,ms) f = (atomically $ writeTChan ms (f a)) >> return (f a,ms)

-- 从结果共享变量中获取结果
fetchS so = atomically $ do{ v <- readTVar so
                           ; case v of
                               Just a -> return a
                               _ -> retry
                           }
蜻蜓点水般不负责任的解释一下STM:
1、核心的数据结构是TVar,它起到了线程间消息通知的作用。它是这样的共享变量,每当从中读取(readTVar)时,如果没有数据,那么就会阻塞,直到读到数据为止。写入(writeTVar)则是立刻执行。
2、STM提供一组Combinator,通过组合构成一个原子操作,使用atomically执行这个原子操作。
3、retry表示在合适的时机重头来执行它所在的原子操作,在这之间保持阻塞。
4、消息共享通道为什么使用TChan。原因是一个数据源有多个观察者,每当一个观察者从数据通道中读到一个新的消息时,它一方面应当将其擦除(或者标记我已经读过了),另一方面它不能影响到其他的观察者从通道中读取这个新的消息。这虽然是一个单写者多读者的问题,但是标记已读的要求使得问题变得复杂。一个可行的方法是写者有一个标记,每次写着写入时,应当顺带修改这个标记,这是一个复杂的想法,实现也很复杂。于是我想到了STM中的TChan。TChan提供给我们这样一个很好的特性:它是一个无限长的管道,写着只管写;读者通过(dupTChan)往这个管道开着窗口,各自维护了自己在管道中的位置,也就是它很清楚自己什么已经读过了,所以它也只管读就可以了。这个很符合我的想法。
June 29

哦耶,金牌

浑水摸鱼,拿了一块某某号称“国际武术邀请赛”的金牌!!顿时觉得人生“辉煌”无比!
June 26

高考招生

今天和一帮同学回一中代表南大参加高考招生资讯会。重温了一中的食堂,小卖部,教学楼,那一个一个瞬间的触动,真是让人怀念阿~~见到了以前的沈老师,可惜曾经的两个班主任都不在学校。我被判定为和四年前毫无变化-_-b。
至于招生资讯会,无非就是一大帮的家长轮番上来问他们家宝贝能不能上阿,南大什么专业最好阿,能不能保证阿等等圈圈叉叉~~由于我等并非招生办最后拍桌之人,所以只能含糊保守的说,"我们估计最低投档**分","你家孩子考的好","应该能上","基本没有问题"等等圈圈叉叉。大概家长最想听到的还是"一定能上"。像"应该","基本",那些踩线的家长总是听得心里悬悬的。期间也有特别牛的同学路过,那种傲视所有专业的气魄还真是了不得。想想四年前自己在同样的时间,同样的地方徘徊于若干的学校的摊点前,那种感觉模糊而真是。现在坐在板凳上,看着一个个家长走过,焦急的询问,觉得自己也算浑浑噩噩的过来四年了,也算是长大一点了。
June 24

看了“黄石的孩子”

发自内心的要向故事的主角乔治•何克和拉达•米契尔表示崇高的敬意。脑海里不时的浮现那一目目感人至深的场景。后来我在豆瓣上看到了这段故事的历史原型,尽管故事内容差别巨大,但是乔治•何克还有路易·艾黎等等在抗战中帮主过中国的国际友人是最最伟大的,值得我们这一代人记忆和腼怀。
June 19

接电话线...

今天猛的接电话线弄了N把,目的是为了ADSL拨号。起因是这两天bras免费流量用光了,没有的国外网站上总觉得不爽,于是今天决定把ADSL掏出来了。这活干的很爽,动动手就是开心阿。
June 16

Macross

早先看过84年的剧场版可曾记得爱,就对Macross作品相当的喜爱。这两日闲着没事,接连着看Macross系列,从82年的Macross一代开始看起,看了Macross Zero,Macross Plus。Macross7,Macross II只看了一小部分。最喜欢的作品还是一代的Macross,故事中里一条辉、林明美、早濑未沙三人间交织的感情在我脑海里留下了浓浓的印象,老实说我似乎更喜欢林明美一些,但是却也很喜欢故事的结尾~~尽管理智的看,这部片做讲的故事似乎荒诞无极,话说一帮巨人成天打仗,终于把地球夷为平地,但是最终其中的一小撮被人类的文化尤其是歌声所感动,于是和地球人修好共创和平盛世。故事就是这样,但是故事里的情感确实是感人至深啊。

June 03

"Composable Memory Transaction"读后感

STM(Software Transactional Memory)是一套基于事务的并行编程模型,相比最最原始的加锁模型有以下优点:
 - 无死锁
 - 良好的组合性

也许以上两大杀手级的优点似乎暗示着STM背后隐藏着复杂的背景知识,但是事实上STM所展现出来却是充分的简洁和明了。所有STM的语句构成一个封闭的环境,这里的意思是说所有的STM语句是STM a类型,语句与语句的组合还是STM a类型。在这个封闭的环境当中能被操作的基本变量是TVar a类型,这样的变量是一个容器,可以存放一个来自外部环境的一个a类型的数据,可以通过几条基本的命令(newTVar, readTVar, writeTVar)来创建、读取、写入一个TVar。这里尽管从外部世界引入了数据,但是在STM语句当中所允许的只有对它们进行没有副作用的运算。简单点讲,STM就是一组对TVar a类型数据的操作,它们可以很好的组合起来,而TVar a就是一个良好的用于各个进程之间的通信的通道。

首先有一条基本性质,STM构成一个Monad,也就是说STM语句可以自由的组合起来使用,构成一个新的语句,它表示这一组STM语句的串行执行。

其次有三条基本的操作是顶顶重要的。第一条,atomic :: STM a -> IO a,可以把它看做执行一个STM语句,它背后蕴含的意思就如其字面所讲,这个STM将是一个原子操作。第二条,retry :: STM a,理解这一条需要回头看看所谓事务模型背后的意思。简单点讲就是执行一个事务时,如果发现了某些不适当的行为或者不满足某些条件时,这个事务将被放弃,而这个事务已经执行的部分所造成的后果将全部撤销。在阻塞或者说等待操作是,基于事务并行模型和基于锁的并行模型有一个不同之处,后者总是需要通过一个条件变量来控制,正确的考虑好在这个条件变量上的操作使得多个线程间正确的扮演好等待者和唤醒者的角色;而前者仅仅检查一下条件,若发现不满足,就放弃当前事务,在适当的时机重试一遍,直到条件满足为止,这也同样完成了阻塞这一效果。这里的retry的意思就是放弃当前事务,在适当的时机重试。第三条,orElse :: STM a -> STM a -> STM a,这个条的目的在于构成一个选择,orElse a b将首先尝试a,如果a放弃了事务,那么尝试一下b,若b也放弃事务,那就放弃整个orElse a b的事务。

明白为什么STM不会造成死锁是很容易的,不妨假设有这样的一条getR :: Resource -> Int -> STM ()函数,他表示从资源中取出指定数目的元素,当资源数目不足时将阻塞,直到有足够的资源为止。那么我们写下这样的一条语句atomic (do {getR r1 3; getR r2 5}),如果发生r2资源不足的情况,整个事务将被放弃,也就是说着整个一条语句将在合适的时候重试一次,从而不会发生这个进程占着r1资源不还,却在死死等待r2资源的情况。这打破了死锁的四条基本条件之一,占有且等待。


View more entries
 
by 
by 
by 
by 
by 
by 
by 
by 
by 
View space
FMiaoZ
View space
niuniu1026
View space
Asura
View space
JJgirl
View space
咖啡
View space
扁鹊扁蔡桓公
View space
View space