单件模式:Singleton那些事
单件模式确保一个类只有一个实例,并提供一个全局的访问点。 –《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访问)
Linode VPS
Tengine