C++单例模式——并非你想的那像简单

Author:WenHui,WuHan University,2012-12-10

前段时间忙着找工作,有一次面试官让我用C++写单例模式,我刷刷刷提笔就来。于是就随手写了如下的代码v1版

 1 class Singlon

 2 { 

 3 private:

 4     static Singlon * inst;

 5 

 6 public:      

 7     static Singlon& instance()

 8     {

 9         if(inst != NULL) 

10         {

11             return *inst;

12         }

13         else 

14         {

15             inst = new Singlon();             

16             return *inst;

17         }

18     } // instance

19 }; // class Singlon

20 

21 Singlon* Singlon::inst = NULL;

 

v1是一份很标准的单例模式代码,但不够实用。面试官看着我,说“你再好好想想...”

我原本很自负,结果被问傻了,紧张之下依稀想起《程序员面试宝典C++版》的内容,好像没考虑多线程问题。于是绞尽脑汁地回想C++中的线程互斥,还好记起点LINUX C下互斥锁,于是乎就有了改进的v2版

 1 class Singlon

 2 { 

 3 private:

 4     static Singlon * inst;

 5     pthread_mutex_t inst_lock;

 6 

 7 public:

 8     static Singlon& instance()

 9     {

10         if(inst != NULL)

11         {

12             return *inst;

13         }

14         else 

15         {

16             pthread_mutex_lock(&inst_lock);

17             if(inst == NULL)

18             {

19                 inst = new Singlon();

20             } // if

21             pthread_mutex_unlock(&inst_lock);

22             return *inst;

23         } // else

24     } // instance

25 }; // class Singlon

26 

27 Singlon* Singlon::inst = NULL;

28 Singlon::inst_lock = PTHREAD_MUTEX_INITIALIZER;

 

v2中首先直接判断inst是否为NULL,若不为NULL则避免调用互斥锁,因为互斥锁不仅使用系统调用,而且将导致多线程竞争等待,产生睡眠。

当写完v2时,终于有种如释重负的感觉,很欣慰,可是面试官冷冷地问我:“你仔细看看,还有什么问题么?”当时脑子一懵,TMD的还有问题?fuck!仔细再check了一下,发现if语句确实还可以有优化的余地,于是胆战心惊地写下了v3版:

 1 class Singlon

 2 { 

 3 private:

 4     static Singlon * inst;

 5     pthread_mutex_t inst_lock;

 6 

 7 public:

 8     static Singlon& instance()

 9     {

10         if(likely(inst != NULL)) 

11         {

12             return *inst;

13         }

14         else 

15         {

16             pthread_mutex_lock(&inst_lock);

17             if(likely(inst != NULL))

18             {

19                 pthread_mutex_unlock(&inst_lock);

20                 return *inst;

21             } // if

22             else 

23             {

24                 inst = new Singlon();

25                 pthread_mutex_unlock(&inst_lock);

26                 return *inst;

27             } // else

28         } // else

29     } // instance

30 }; // class Singlon

31 

32 Singlon* Singlon::inst = NULL;

33 Singlon::inst_lock = PTHREAD_MUTEX_INITIALIZER;

 

v3由于互拆锁会导致效用问题,越尽早释放越好,所以将一条if语句拆成两条、迟早释放,并且将条件概率大的情况放到if而非else中,用likely在汇编层上优化。我感觉这次大体上应该符合面试官要求了,但是面试官继续追问,“我再给你一次机会好好想想……”当时正值午餐时间,早晨没吃饭,肚子饿得咕咕叫,脑子都快疯了。幸好最后换了个思路,写下v4版:

 1 class Singlon

 2 { 

 3 private:

 4     static Singlon * inst;

 5 

 6 public:

 7     static Singlon& instance()

 8     {           

 9         return *inst;

10     } // instance

11 }; // class Singlon

12 

13 Singlon* Singlon::inst = new Singlon();

 

当提交给面试官看时,心想”今天真是倒霉”,幸而走屎运,v4预先创建好一个对象。虽然浪费空间,但不用再考虑TMD的线程互斥问题。面试官看了之后,语重心长地讲“你这个浪费空间太大,我要你实现初次使用时再申请。给你最后一次机会检查检查代码。”

晕!当时感觉已经能用的招都用了,后来放弃了,根他解释了下v3版代码,然后问他到底哪有问题。他说:“你为什么要使用互斥锁?”于是给他解释LINUX下互斥锁的实现机制。MutexLock使用SpinLock实现互斥,循环检测并休眠。当时突然想起可以直接用SpinLock实现v3的互斥,因为竞争仅发生一次,SpinLock直接采用循环检测不休眠的方式,使用“内存屏障”、“CPU屏障”等技术保证内存和CPU指令的“有序”。v5版如下: 

 1 class Singlon

 2 { 

 3 private:

 4     static Singlon * inst;

 5     static raw_spinlock_t inst_lock;

 6 

 7 public:

 8     static Singlon& instance()

 9     {

10         if(likely(inst != NULL)) 

11         {

12             return *inst;

13         }

14         else 

15         {

16             spin_lock(&inst_lock);

17             if(likely(inst != NULL))

18             {

19                 spin_unlock(&inst_lock);

20                 return *inst;

21             } // if

22             else 

23             {

24                 inst = new Singlon();

25                 spin_unlock(&inst_lock);

26                 return *inst;

27             } // else

28         } // else

29     } // instance

30 }; // class Singlon

31 

32 Singlon* Singlon::inst = NULL;

33 spin_lock_init(Singlon::inst_lock);

 

面试官看完v5版之后,问spin_lock的实现原理,由于看过LINUX内核怎么实现所以说得比较清楚。听完我的解释,当时已经中午一点多,他也没有再多说什么,终于结束这个该死的问题。其实C++没有像JAVA和C#那样从语法上提供同步互斥的机制,我中间也表示希望换成C#,但他拒绝了,因为自我简介时说精通C++。唉,~~~~~~

其实,v5版本还可以优化,例如采用无锁的办法。但总得说来,只是将spin_lock的代码简化了一下内嵌进来。不知道是否有哪位高人可以指点一二,给一个最终优化的C++版单例模式?


《无锁队列的实现》,http://www.kuqin.com/algorithm/20120907/330193.html

 

 

 

你可能感兴趣的:(单例模式)