单例模式在类只允许一个实例时用到
以下是摘自“Design Patterns Elements of Reusable Object-Oriented Software”一书中单例模式实现:
class Singleton {
public:
static Singleton* Instance();
protected:
Singleton(){};
private:
static Singleton* _instance;
};
Singleton* Singleton::_instance = 0;
Singleton* Singleton::Instance () {
if (_instance == 0) {
_instance = new Singleton();
}
return _instance;
}
在Singleton::Instance ()中,_instance的检测与修改不是原子操作,存在竞态条件
当多个线程同时检测_instance,且此时_instance=0,这些线程都会进入if控制块,new会到堆中去为Singleton分配内存;由于这些线程都为Singleton分配了内存,且只有一个会赋值到_instance,其它的就无法引用而出现内存泄露。
用以下例子来扩大化问题,因内存泄露而造成进程的coredump
/* singletonrace.cpp */
1 #include <iostream>
2 #include <cstdlib>
3 #include <sstream>
4 #include <cerrno>
5 #include <cstring>
6
7 #define MAX 1024
8 #define ARRAYSIZE 1024*1024*50
9
10 class Singleton {
11 public:
12 static Singleton* Instance(int nsecs);
13 protected:
14 Singleton();
15 private:
16 static Singleton* _instance;
17 int array[ARRAYSIZE];
18 };
19
20 Singleton* Singleton::_instance = 0;
21
22 Singleton::Singleton() { }
23 /*
24 Singleton::Singleton() {
25 int idx;
26 for (idx = 0; idx < ARRAYSIZE; idx++) {
27 array[idx] = 0;
28 }
29 }
30 */
31
32 Singleton* Singleton::Instance (int threadId) {
33 if (_instance == 0) {
34 ::sleep(threadId%32 + 3);
35 _instance = new Singleton();
36 }
37 return _instance;
38 }
39
40 int runningThread = 0;
41
42 void * fun(void *id){
43 runningThread++;
44 int threadId = *(int *)id;
45 Singleton::Instance(threadId);
46 runningThread--;
47 }
48
49 int main(int argc, char *argv[])
50 {
51 int idx = 0;
52 pthread_attr_t thrattr;
53 pthread_t thr[MAX];
54 int id[MAX] = {0};
55 int err;
56
57 if (argc != 2) {
58 std::cout << "bad command\ncommand:" << argv[0] << " cnt" << std::endl;
59 return -1;
60 }
61 int cnt = atoi(argv[1]);
62 if (cnt < 0 || cnt > MAX) {
63 cnt = MAX;
64 }
64 }
65
66 if (err = pthread_attr_init(&thrattr) != 0) {
67 std::cout << "pthread_attr_init error:" << std::strerror(err) << std::endl;
68 return -1;
69 }
70 if (err = pthread_attr_setstacksize(&thrattr, 102400) != 0) {
71 std::cout << "pthread_attr_init error:" << std::strerror(err) << std::endl;
72 return -1;
73 }
74
75 while(idx < cnt) {
76 id[idx] = idx;
77 if (err = pthread_create(&thr[idx], &thrattr, fun, &id[idx]) != 0) {
78 std::cout << "thread " << idx << " create faild:" << std::strerror(err) << std::endl;
79 std::cout.flush();
80 sleep(1);
81 } else {
82 std::cout << "thread " << idx << " created" << std::endl;
83 std::cout.flush();
84 idx++;
85 }
86 }
87
88 do {
89 sleep(1);
90 std::cout << "runningThread:" << runningThread << std::endl;
91 } while(runningThread);
92
93 std::cout << "exit without coredump" << std::endl;
94 return 0;
95 }
Makefile
all:
g++ -g singletonrace.cpp -lpthread -o singletonrace
Singleton类中成员array大小是sizeof(int)*ARRAYSIZE = 200M,所以最多15个Singleton实例就会造成coredump(用户进程地址空间3G/200M=15)
[redhat@localhost singleton]$ ./singletonrace 15
thread 0 created
thread 1 created
thread 2 created
thread 3 created
thread 4 created
thread 5 created
thread 6 created
thread 7 created
thread 8 created
thread 9 created
thread 10 created
thread 11 created
thread 12 created
thread 13 created
thread 14 created
runningThread:15
runningThread:15
runningThread:14
runningThread:13
runningThread:12
runningThread:11
runningThread:10
runningThread:9
runningThread:8
runningThread:7
runningThread:6
runningThread:5
runningThread:4
runningThread:3
runningThread:2
terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
已放弃 (core dumped)
从以上报错可以看出new分配失败,而结束线程组(进程)
可以用以下命令看到内存不断的增涨,coredump后内存回到原来的水平:
top -p `ps -ef | grep "./singletonrace" | sed /grep/d | awk '{ print $2 }'`
为了让top中内存变化更明显,在Singleton构造函数中初始化array为全0,这样内核就会为Singleton实例分配物理内存;如果没有初始化array动作,top时内存变化很小,因为new只是获取的进程地址空间的使用权,内核并没有为其分配物理内存。
竞态条件的规避:
1.在程序启动,只有主控线程时调用Singleton::Instance(),由于只有主控线程即进程只有一条执行路径,所以不存在竞态条件
2.将_instance的检测与分配放入pthread_mutex_t锁保护内,这样做会影响运行效率及增大代码复杂度;java中用synchronize关键字保护_instance的检测与分配
3.Singleton类非常小,且出现内存泄露的概率极小,人为的去忽视该内存泄露。
附:
gdb查看造成coredump原因:
[redhat@localhost singleton]$ ulimit -c
unlimited
[redhat@localhost singleton]$ gdb singletonrace core.11263
GNU gdb (GDB) Red Hat Enterprise Linux (7.2-48.el6)
Copyright (C) 2010 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "i686-redhat-linux-gnu".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/redhat/code/cpp/singleton/singletonrace...done.
[New Thread 11277]
[New Thread 11278]
[New Thread 11263]
Missing separate debuginfo for
Try: yum --disablerepo='*' --enablerepo='*-debuginfo' install /usr/lib/debug/.build-id/92/20b9faae8b6444629162d83177c1e4a533f647
Reading symbols from /lib/libpthread.so.0...(no debugging symbols found)...done.
[Thread debugging using libthread_db enabled]
Loaded symbols for /lib/libpthread.so.0
Reading symbols from /usr/lib/libstdc++.so.6...(no debugging symbols found)...done.
Loaded symbols for /usr/lib/libstdc++.so.6
Reading symbols from /lib/libm.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libm.so.6
Reading symbols from /lib/libgcc_s.so.1...(no debugging symbols found)...done.
Loaded symbols for /lib/libgcc_s.so.1
Reading symbols from /lib/libc.so.6...(no debugging symbols found)...done.
Loaded symbols for /lib/libc.so.6
Reading symbols from /lib/ld-linux.so.2...(no debugging symbols found)...done.
Loaded symbols for /lib/ld-linux.so.2
Core was generated by `./singletonrace 15'.
Program terminated with signal 6, Aborted.
#0 0x00f58424 in __kernel_vsyscall ()
Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.25.el6.i686 libgcc-4.4.5-6.el6.i686 libstdc++-4.4.5-6.el6.i686
(gdb) bt
#0 0x00f58424 in __kernel_vsyscall ()
#1 0x0017ad71 in raise () from /lib/libc.so.6
#2 0x0017c64a in abort () from /lib/libc.so.6
#3 0x0604d327 in __gnu_cxx::__verbose_terminate_handler() () from /usr/lib/libstdc++.so.6
#4 0x0604b186 in ?? () from /usr/lib/libstdc++.so.6
#5 0x0604b1c3 in std::terminate() () from /usr/lib/libstdc++.so.6
#6 0x0604b302 in __cxa_throw () from /usr/lib/libstdc++.so.6
#7 0x0604b897 in operator new(unsigned int) () from /usr/lib/libstdc++.so.6
#8 0x080488d3 in Singleton::Instance (threadId=13) at singletonrace.cpp:35
#9 0x08048917 in fun (id=0xbf8ba3a4) at singletonrace.cpp:45
#10 0x002ef9e9 in start_thread () from /lib/libpthread.so.0
#11 0x0022e0fe in clone () from /lib/libc.so.6
(gdb) frame 9
#9 0x08048917 in fun (id=0xbf8ba3a4) at singletonrace.cpp:45
45 Singleton::Instance(threadId);
(gdb) list 45
40 int runningThread = 0;
41
42 void * fun(void *id){
43 runningThread++;
44 int threadId = *(int *)id;
45 Singleton::Instance(threadId);
46 runningThread--;
47 }
48
49 int main(int argc, char *argv[])
(gdb) print runningThread
$2 = 2
(gdb) print threadId
$3 = 13
(gdb)
由coredump时的堆栈信息可以看出,进程异常退出是因为new分配内存失败:
clone系统调用创建轻量级进程,并执行pthread库中的start_thread,start_thread调用singetonrace.cpp中的fun函数(用户自定义线程函数,从内核角度来看线程函数是start_thread);
第threadId=13个线程获取实例时失败,原因是new分配内存失败并抛出异常,new捕获到异常由std::terminate()结束进程;
std::terminate()通过调用abort与raise产生coredump(SIGABRT 6 Core Abort signal from abort(3))并结束进程。
以上堆栈信息是造成线程组(进程)异常退出线程的,现在进程还有主线程和threadId=14线程(都在睡眠中),其它线程均已正常退出:
(gdb) info thread
3 Thread 0xb77606d0 (LWP 11263) 0x00f58424 in __kernel_vsyscall ()
2 Thread 0xb7600b70 (LWP 11278) 0x00f58424 in __kernel_vsyscall ()
* 1 Thread 0xb7619b70 (LWP 11277) 0x00f58424 in __kernel_vsyscall ()
(gdb) thread 2
[Switching to thread 2 (Thread 0xb7600b70 (LWP 11278))]#0 0x00f58424 in __kernel_vsyscall ()
(gdb) bt
#0 0x00f58424 in __kernel_vsyscall ()
#1 0x001edb46 in nanosleep () from /lib/libc.so.6
#2 0x001ed970 in sleep () from /lib/libc.so.6
#3 0x080488c7 in Singleton::Instance (threadId=14) at singletonrace.cpp:34
#4 0x08048917 in fun (id=0xbf8ba3a8) at singletonrace.cpp:45
#5 0x002ef9e9 in start_thread () from /lib/libpthread.so.0
#6 0x0022e0fe in clone () from /lib/libc.so.6
(gdb) thread 3
[Switching to thread 3 (Thread 0xb77606d0 (LWP 11263))]#0 0x00f58424 in __kernel_vsyscall ()
(gdb) bt
#0 0x00f58424 in __kernel_vsyscall ()
#1 0x001edb46 in nanosleep () from /lib/libc.so.6
#2 0x001ed970 in sleep () from /lib/libc.so.6
#3 0x08048c4d in main (argc=2, argv=0xbf8bc464) at singletonrace.cpp:89
(gdb)