zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    Codeforces
    (水题)Codeforces
    【拓展欧几里得】方程的解
    洛谷P3216 [HNOI2011]数学作业
    洛谷P1719 最大加权矩形
    洛谷P1369 矩形
    洛谷 P1236 算24点
    洛谷P2014 选课
    洛谷 P1573 栈的操作
    洛谷P1531 I Hate It
  • 原文地址:https://www.cnblogs.com/icanth/p/2811855.html
Copyright © 2011-2022 走看看