杂谈Singleton模式,Monostate以及DCL
[目录]
一句话简介Singleton
局限性
实现手法
优缺点和问题
MonoState vs Singleton
Mutithreading: ACE中的DCL
DCL的应用条件
[前面的一陀]
(明明目录中没有,但还是想写,看来但凡写点文章或者翻译些文章的人,都会想到要搞一陀?)
Singleton可是面试时的老朋友了——平均每个月都会有那么几次
有些面试题上面写着:Apart from Singleton, descirbe a Design Pattern below —— 英语不好的达淫可能要吃亏了
有些应试者在被问到Design Pattern的时候,憋了半天也只能说出一个Singleton —— 于是面瓜(官)们面露邪恶地微笑,满心欢喜道:早就料到你小子不行,哼哼
……
扯点稍远的话题,其实Design Pattern也就是在C风格的语言中显得如此地必要、如此地“经典”、如此地“神圣”……甚至于有了Design Pattern的metaphysics:
——只要你使用了Design Pattern,你就可以做得很好;
——而你做得不够好,是因为你没有使用Design Pattern!
(大家可以联想一下戈培尔的“元首版”)
(计算机编程语言只有2种风格,一种是C语言风格,另一种是LISP风格,像Java语言如此之成功,无外乎是在C风格的基础上向LISP借鉴了一些“智能”因素,这也是近些年来语言设计的万灵大法和花葵红宝典,比如Garbage Collection/ Mix-in/ Closure/ AMB/ Continuation ... etc etc etc)
设计模式在C++/Java语言和社区中很有地位,但是在LISP/SCHEME等语言中,Design Pattern就是一陀陀碍眼的米田共。不信,你可以试着写一个Double-Dispatching Visitor,再看看LISP中的multimethod功能(定义generic function)有多么强大,最后再决定是不是要用C++/Java这样的旧石器时代的工具打造一个Triple-Dispatching Visitor。附带说一句,连JavaScript都把Generic Function添加到ECMA标准中了。
——如果以上的话语深深地伤害了无辜的PLoP fans和模式社区,请将矛头对准Peter Norvig的这个Slide(http://norvig.com/design-patterns/)。
这位名叫Peter Norvig的老大你肯定听说过
——如果没听说过,那你肯定看过《十年编程无师自通》那篇文章
——如果你连那篇文章也没看过,那请按一下键盘上的Alt键,然后按空格键,再按字母C。
P.S. 另一个例子是Observer和Before/After
[一句话简介Singleton]
终于可以开始本文的正题了。
Singleton是一种惯用写法,它让你可以通过一个小辨子将幕后的大BOSS揪出来。
[局限性]
既然它只是一种惯用写法,那么它能带来的好处也就不会太多,顶多和其它“写法”带来的好处一样多。这方面典型的例子是误以为Singleton会跨进程、跨线程起作用,比如用它禁止多实例启动。
Singleton的实现和使用上也是有比较多的麻烦,比如:
A) 书写方便性问题:每个需要singleton的类型都需要单独书写名为singleton的一陀
B) 模板编译问题:如果为了解决书写方便性问题,使用了模板进行辅助(比如使用LOKI: http://sourceforge.net/projects/loki-lib/ ),那么,偶尔碰上编译器生成了singleton的多份内存实例会头痛得要死(一般仅仅在release时发生)
C) 实例化顺序问题:要维护和保证50个singleton对象的产生和释放按要求的顺序进行,可不是一件容易的事情。
D) 又臭又长的程序行:如果一个函数调用,以及它的若干参数都是通过singleton来取得的,那这一行肯定丑陋不堪。作为一个合格的程序员绝不能坐视这种问题。
Sorry,上面的几条俺自己也有点看不下去。归根结底是因为俺实再是不习惯列举1234这样的条目。初中时背政治题,左一条右一条的那种列举题俺一向是不在行。脑子里想如果答题时也有car/cdr那该多么好呀多么好……
[实现手法]
暂略,寻找面筋的可以离开这个页面了。(当然,你要是将上面的一大段侃给面瓜听效果也会不错,关键看你能不能争取得到很多自由发言的时间)
[优缺点和问题]
优点不提,缺点也懒得再说——这个目录编得比较失败的说。
[MonoState vs Singleton]
终于来了点提神的,可以说点“不为人知的秘密”(80年代飘柔广告语,估计会有人想起那一群空姐窃窃私语然后笑靥如花的画面)
Singleton的写法在某些情况下显得又臭又长,怎么解决?作为编程问题,往往是答案早已摆在某处,所以多数程序员都喜欢用google之类的东西去“复用”一下。PLoP的达人们可以忽略本节了,俺是在看了BOB大叔的书(http://www.objectmentor.com/omTeam/martin_r.html)之后才知道有这个模式的。
先看看这一行:
PrinterService::getPrinter().Print(PrinterConfigService::getPrinterConfig(), "How long gone are you gonna be/ Brooks & Dunn, 1998/n");
上面这一行是Singleton牌的,OEM来自王母娘娘。
再来看这一段:
PrinterService printer;
PrinterConfigService config;
printer.Print(config, "Born & Raised in Black & White/ Brooks & Dunn, 1998/n");
上面的来自于MonoState。
其实只是写法不同,背后的操作策略都是一样的。MonoState这个名字本来的意义就是指,同一个类型,但是所有不同的对象共享同样的“单一状态”。
MonoState更符合人的思路,也更符合人们的习惯。实际上,打印机这东西,很少有人按Singleton去写的,多数是MonoState
(至于为什么“隐藏在背后的操作都一样”,我偏偏不说,天知,地知,我知,还有小坏狗知……)
[Mutithreading: ACE中的DCL]
如果我不写什么是DCL,只给出Douglas c. Schmidt的这个文章的链接,会不会有鸡蛋扔上来呢?先试试,没有反对的我就这么着,可以省事儿了——反正看我BLOG()的人估计不会太多。
道格拉斯童鞋的文章在这里:http://ftp.icm.edu.pl/packages/ace/ACE/PDF/DC-Locking.pdf<未完待插>
[DCL的应用条件]
DCL在Java中无效。C++的粉丝们可以大笑了。Anyway, anyhow, yet another shortcoming of Java.
可是,等一下,为什么?为什么这个在逻辑上行得通的DCL,这个任何程序员都可以人肉演算的程序片段,在Java中就不行?难道Java违背了社会的普遍真理,抛弃了辩证唯物主义和历史唯物主义,走到了反人类的歧路上?
每一个编译优化都是痛苦艰辛而且不讨好的。说它“痛苦艰辛”,是因为“编译器的效能每18年翻上一倍”(这个叫做Proebsting's Law,参见http://research.microsoft.com/~toddpro/)。说它“不讨好”,是因为编译器优化常常“好心干坏事”,自作聪明地为了性能牺牲质量(萝卜快了不洗泥之计算机版?)。
为什么要讨论DCL的失效呢?(像这样的问句,在这种文章里都会有一个答案。这种修辞手法叫做设问,也就是自问自答——“问”是为了“答”;“答”是回应“问”。说白了就是“吃多了撑的、装着明白犯糊涂”)其实,我是想要引出一个很邪恶很强大的东东,这个东西叫做CAS。
但是在说CAS之前,我还得要先说点别的东东。(这在说书的时候叫“花开两朵、各表一枝”,也可以叫“卖关子”)
——如果你写过exception-safe的程序,你肯定知道有一个手法——Bingo! 但是你猜错了,不是RAII,而是ABR Idiom。ABR是Acquisition Before Release,也就是说先把新的资源搞定了,然后用一个no-exception guarantee的swap将新老资源的进行交换(例如:C++中的指针交换或者STL container的swap函数),然后对老资源进行释放,这个是针对Strong Exception-Safe Guarantee的常用手法(参考一下这里:http://www.accu-usa.org/2001-03-Main.html)。
写点代(伪)码来表达一下上述的东西:
// The following lines guarantee Strong Exception Safety
extern Vector oldVal;
Vector newVal = new Vector(oldVal); // Acquisition
newVal.push_back(Element("param")); // Push a new element into the vector
oldVal.swap(newVal); // No exception will be thrown from this function
// The destructor of newVal will release elements prior in oldVal
如果你能体会得到这种思路的优越性,那么恭喜你,你已经悟道了。
现在,我们回过头来说CAS(http://www.ddj.com/cpp/184401865)。CAS是Generic<Programming>专栏的Andrei Alexandrescu小盆友发明的东东,作为专栏作者,Andrei童鞋是有权发明新的缩写的,这个CAS就是Compare And Swap的缩写。下面的伪码(注意不是代码)来自于Andrei的文章:
template <class T>
bool CAS(T* addr, T expected, T value) {
if (*addr == expected) {
*addr = value;
return true;
}
return false;
}
需要再次强调的是,上面的几行是“伪码”。因为这个函数是用来“示意”的,实际上这是一个原子操作——这的的确确是如假保换,即使是多CPU也不会打破的原子操作(手持Alpha射线枪的、卢瑟福之类的小人闪到一边去!!!)。Andrei从Intel处理器中挖出了一条叫CMPXCHG8的汇编指令说可以实现这种操作,不过汇编这东东我还是不在行,说多了献丑。于是就此打住。好了,现在全文终,大家可以洗洗睡了……
嗯,以上一句作废。接下来想说的是:用这个CAS原子操作可以解决DCL问题。
<未完待插>
[结论]
由上可见,多简单的东西都可以拿来侃。Over.
[关于作者]
Kenny Yuan,从1990年开始学习编程。目前对算法,语言,心理学和综合格斗感兴趣。
[声明]
本文写于2008年8月,文中涉及的技术性内容随时间变化可能会有变化(特别是某些观点、技术限制等)。