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