存档

文章标签 ‘Python’

单件模式:Singleton那些事

2010年3月1日 Zealot Comments

单件模式确保一个类只有一个实例,并提供一个全局的访问点。 –《Head First 设计模式》

有些时候,我们恰好需要这样一种设计,保证我们实例化的类在进程地址空间内只有一个实例,有了Singleton,一切似乎都简单明了,其实暗藏杀机。

1. C++实现Singleton模式简单示例

简单的C++实现示例代码

class Singleton {
    public:
        static Singleton* GetInstance(void) {
            if (Singleton::m_instance == NULL) {
                Singleton::m_instance = new Singleton();
            }
            return Singleton::m_instance;
        }
    private:
        static Singleton* m_instance;
        Singleton() {}
};

使用gtest测试,简单地测试一下每次调用GetInstance是否返回同一地址

#include 
#include 

#include 
using namespace std;
#define NEW_COUNT 4096

class Singleton {
    public:
        static Singleton* GetInstance(void) {
            if (Singleton::m_instance == NULL) {
                Singleton::m_instance = new Singleton();
            }
            return Singleton::m_instance;
        }
    private:
        static Singleton* m_instance;
        Singleton() {}
};

Singleton* Singleton::m_instance = NULL;

TEST(Singleton, sample) {
    Singleton* init_instance = Singleton::GetInstance();
    ASSERT_NE(init_instance, (Singleton*)NULL);

    Singleton* p = NULL;
    ASSERT_EQ(p, (Singleton*)NULL);

    for (int i = 0; i < NEW_COUNT; ++i) {
        p = Singleton::GetInstance();
        ASSERT_EQ(init_instance, p);
    }
}

int main(int argc, char **argv)
{
    ::testing::InitGoogleTest(&argc, argv);
    return RUN_ALL_TESTS();
}

测试结果显示没有问题:
$ ./singleton
[==========] Running 1 test from 1 test case.
[----------] Global test environment set-up.
[----------] 1 test from Singleton
[ RUN ] Singleton.sample
[ OK ] Singleton.sample (1 ms)
[----------] 1 test from Singleton (1 ms total)

[----------] Global test environment tear-down
[==========] 1 test from 1 test case ran. (2 ms total)
[ PASSED ] 1 test.

当然,这种测试没有太大意义。给出测试结果,主要用于之后的对比。

2. python程序中调用Singleton模式的C++代码
初次接触Singleton后发现,使用Singleton模式的C++代码融合到python中有意想不到的惊喜:如果有某个类初始化需要很长时间,而之后调用的速度都很快的话,使用Singleton模式,然后在python中多次调用时,不会出现重复的初始化操作。
简单地尝试了一下

首先在singleton.cpp中添加了一行输出,标识一次初始化操作:

Singleton* Singleton::GetInstance(void) {
    if (Singleton::m_instance == NULL) {
        Singleton::m_instance = new Singleton();
        std::cout << "initialize here." << std::endl;
    }
    return Singleton::m_instance;
}

封装的代码如下
$ cat _pysingleton.cpp -n

#include 

#include "singleton.h"

using namespace std;

PyObject * GetInstance(PyObject *self, PyObject *args) {
    Singleton::GetInstance();
    return Py_BuildValue("s", NULL);
}

static PyMethodDef _pysingletonMethods[] = {
    {"GetInstance", GetInstance, METH_VARARGS, "get instance."},
    {NULL, NULL},
};

PyMODINIT_FUNC init_pysingleton(void) {
    PyObject * m;
    m = Py_InitModule("_pysingleton", _pysingletonMethods);
}

编译出来共享库_pysingleton.so
$ cat Makefile -n

CC                =       g++
FLAGS           =       -ggdb -O2 -Wall
INC               =       -I/usr/include/python2.5
LIB               =       -lgtest -lpython2.5
LLIB              =

%.o:%.cpp
        $(CC) $(FLAGS) $(INC) -c $<

_pysingleton.so: singleton.o _pysingleton.o
        $(CC) -shared $(CFLAGS) $(LLIB) $(INCLUDE) $^ -o $@

clean:
        rm -f *.so
        rm -f *.o
        rm -f *.pyc
        rm -f singleton

写个简单的python脚本测试一下在单一进程空间里,是否真的只调用一次
$ cat pysingleton.py -n

#!/usr/bin/env python
# -*- coding: utf-8 -*-

''' sample for pysingleton
'''

import os
import sys
import _pysingleton

class PySingleton:
    def __init__(self):
        _pysingleton.GetInstance()

def main():
    ''' main function
    '''
    for i in xrange(3):
        PySingleton()
    print 'Done'

if __name__ == '__main__':
    main()

结果是
$ ./pysingleton.py
initialize here.
Done

3. 多线程中使用Singleton模式
多线程里就会遇到一个double-check问题 (double-check-lock problem, DCLP)
首先,静态变量m_instance作用域是全局的,处于临界区,在多线程需要加锁。这一点很好理解。
但是,简单的在if外面加锁势必会影响性能,毕竟只有第一次调用才会生效,每次都有一次锁操作太费时,也没必要。
于是,就会有double-check方法了,伪代码为
if (Singleton::m_instance == NULL) // first check
lock
if (Singleton::m_instance == NULL) // second check

从这里可以看出,double-check有效地避免了重复锁操作问题。
然而,问题还没有结束。采用double-check的话,又会引入另一个问题。在这里
Singleton::m_instance = new Singleton();
编译器可能会做个优化,首先将一个有效的内存地址返回给m_instance,然后再调用构造函数初始化。这样很有下一次double-check中首次检查通过,但是得到的内存地址指向的是一个未经初始化的类。后果怎么样,只有天知道了。
更多细节,参考这里:http://www.ibm.com/developerworks/library/j-dcl.html

a. 最简单的修改方法是
Singleton* temp = new Singleton();
m_instance = temp;

但是一个自作聪明地编译器还是会把结果优化成难以想象的样子
b. 另一个方法就是使用volatile关键字,用以声明不受编译器优化的影响
比如 Singleton* volatile temp = new Singleton();
但是远远不够,temp是volatile,但是*temp呢,temp->foo,temp->bar...这些成员呢?所以,要改的话,得把全世界都改成volatile,除了那个lock-_-|||

4. 以上都只考虑单核,再考虑一下多核机器,问题将会更加复杂

总结:珍爱生命,远离Singleton。尤其是不要把Singleton放进通用库中,还有C++中那个叫template的高级玩意儿,都搞在一起老天都很难保证会发生些什么。


1. 这里也在讨论单例模式Why Singletons Are Controversial
2. 代码托管到Google Code,欢迎围观、讨论、批评、挑bug:点此进入(http://code.google.com/p/zldemo/source/browse/#svn/trunk/blog/singleton_everything)
(墙内用户请不要使用https访问)
--EOF--

p.s. 从 DBA Notes偷来EOF,顿感清爽不少

Python罕见秘笈搜集

2008年10月13日 Zealot Comments

features of Python

From: http://stackoverflow.com/questions/101268/hidden-features-of-python

Chaining comparison operators:

>>> x = 5

>>> 1 < x < 10

True

>>> x < 10 < x*1...

分类: Python, linux 标签: , ,

贴个python代码测试下coolcode插件

2008年5月25日 Zealot Comments

python utils
平时写的几个python util,包括了读写文件和http下载;另外的dprint用于调试的,没办法,python print中文list时没办法直接显示
# -*- coding: gbk -*-
#!/usr/bin/env python2.4
import...

分类: Python 标签: , ,