Singleton pattern is very simple?

Singleton pattern looks simple, but has a lot of issues to be considered.
In software systems, often have such special classes, they must ensure that there is only one instance in the system, in order to ensure their logical correctness, and good efficiency.
Therefore we have to consider how to bypass the filter of conventional construction (not allow a new user object), there is provided a mechanism to ensure that only one instance of a class.

Scenario:

  • The Windows Task Manager (Task Manager) is a typical example of the single mode, you can not open both Task Manager. Windows Recycle Bin is the same reason.
  • Application log application, can generally be achieved with a single mode of embodiment, only one instance of the file to operate.
  • Reads the configuration file, read configuration items are public, a place to read everywhere can use, no need to read it again all over the place can be configured.
  • Database connection pooling, multi-threaded thread pool.

    achieve

    Achieve Singleton pattern has a lot of, let's look at some common implementation. Some implementations may be suitable for some scenes, but it does not mean that can not be used.

Achieve a thread-unsafe []

class Singleton{
public:
    static Singleton* getInstance(){
        // 先检查对象是否存在
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
        return m_instance;
    }
private:
    Singleton(); //私有构造函数,不允许使用者自己生成对象
    Singleton(const Singleton& other);
    static Singleton* m_instance; //静态成员变量 
};

Singleton* Singleton::m_instance=nullptr; //静态成员需要先初始化

This is the most classic singleton implementation, the constructor and copy constructor are set to private, but also uses a lazy initialization way, the object will be generated at the time of the first call getInstance () is not called on does not generate the object, it does not occupy memory. However, in the case of multi-threading, this method is not secure.

Analysis: Under normal circumstances, if thread A call to getInstance (), the m_instance initialized, the thread B then call getInstance (), will not perform the new, and the constructed object is returned directly before. However, there is such a case, the thread A executes m_instance = new Singleton () is not yet complete, this time m_instance still nullptr, thread B is also being implemented m_instance = new Singleton (), which is will have two objects, A and thread B may be using the same object, it could be two objects, which may lead to procedural errors, while also memory leak occurs.

[Achieve two thread-safe, lock the price is too high]

//线程安全版本,但锁的代价过高
Singleton* Singleton::getInstance() {
    Lock lock; //伪代码 加锁
    if (m_instance == nullptr) {
        m_instance = new Singleton();
    }
    return m_instance;
}

Analysis: The wording of the above situation does not occur two new execution threads, and thread A when performing m_instance = new Singleton (), the thread B if you call the getInstance (), will be blocked in the locked office, A waiting thread execution after the release of the lock. So are thread-safe.

However, the performance of such an approach is not high, because each call to getInstance () will lock to release the lock, and this step is only the first new Singleton () is necessary, as long as m_instance is created, no matter how many threads At the same time access, use if (m_instance == nullptr) are judged adequate (just read operation, no lock), no thread safety issues, but added there is a lock after the performance issues.

[Achieve three double checking locks]

The above approach is to just-do, when you want to access a thread, the first lock again, this will lead to unnecessary consumption of the lock, then, is it possible to determine the next if (m_instance == nullptr) it, If so, you do not need to explain lock ah! This is the idea of ​​the so-called double-checked locking (DCL) of, DCL that is double-checked locking.

//双检查锁,但由于内存读写reorder不安全
Singleton* Singleton::getInstance() {
    //先判断是不是初始化了,如果初始化过,就再也不会使用锁了
    if(m_instance==nullptr){
        Lock lock; //伪代码
        if (m_instance == nullptr) {
            m_instance = new Singleton();
        }
    }
    return m_instance;
}

This looks great! Used only lock the first time necessary, and after it a realization in the same.

For a long period of time, a lot of people confused, in 2000, when had been found loopholes, but also in each language are found. The reason is out of order (question compiler) memory read and write.

Analysis: m_instance = new Singleton () the sentence can be divided into three steps are performed:

  • Singleton memory is allocated a type of object required.
  • Singleton construct the object type of the memory allocation.
  • The memory address allocated to the pointer m_instance.

Might think that these three steps are executed sequentially, but in fact can only be determined in step 1 is executed first, the steps 2, 3, not necessarily. The problem arises here. If, when a thread A executed in the call m_instance = new Singleton () is in the order of 3,2, then executing the step 3 just Singleton type is assigned to a memory (not nullptr the case m_instance) switches to thread B, is not due m_instance nullptr, so thread B is executed directly return m_instance get an object, and this object does not really being constructed! ! Serious bug just happened.

The compiler may have to adjust and optimize the code, the code looks for those who order does not affect the result of the adjustment of the order, but in fact may be a problem in multiple threads.

Achieve four [cross-platform C ++ 11 version of realization]

After java and c # find this problem, add a keyword volatile, m_instance when declaring variables, to add volatile modification, after the compiler sees, you know this place can not reorder (be sure to allocate memory in implementation of the constructor, and then after the assignment have been completed).

As for the c ++ standard but has not been corrected, so the VC ++ version 2005 also joined this keyword, but it does not cross-platform (only supports the Microsoft platform).

And to the c ++ version 11, finally we have such a mechanism to help us achieve cross-platform solutions.

//C++ 11版本之后的跨平台实现 
// atomic c++11中提供的原子操作
std::atomic<Singleton*> Singleton::m_instance;
std::mutex Singleton::m_mutex;

/*
* std::atomic_thread_fence(std::memory_order_acquire); 
* std::atomic_thread_fence(std::memory_order_release);
* 这两句话可以保证他们之间的语句不会发生乱序执行。
*/
Singleton* Singleton::getInstance() {
    Singleton* tmp = m_instance.load(std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_acquire);//获取内存fence
    if (tmp == nullptr) {
        std::lock_guard<std::mutex> lock(m_mutex);
        tmp = m_instance.load(std::memory_order_relaxed);
        if (tmp == nullptr) {
            tmp = new Singleton;
            std::atomic_thread_fence(std::memory_order_release);//释放内存fence
            m_instance.store(tmp, std::memory_order_relaxed);
        }
    }
    return tmp;
}

Pthread_once achieve five [function]

In linux, pthread_once () function ensures that a function is executed only once.

statement:

int pthread_once(pthread_once_t once_control, void (init_routine) (void));

Function: This function uses the initial value of once_control PTHREAD_ONCE_INIT of
variables to ensure init_routine () function is performed only once in this process execution sequence.
Examples are as follows:

class Singleton{
public:
    static Singleton* getInstance(){
        // init函数只会执行一次
        pthread_once(&ponce_, &Singleton::init);
        return m_instance;
    }
private:
    Singleton(); //私有构造函数,不允许使用者自己生成对象
    Singleton(const Singleton& other);
    //要写成静态方法的原因:类成员函数隐含传递this指针(第一个参数)
    static void init() {
        m_instance = new Singleton();
      }
    static pthread_once_t ponce_;
    static Singleton* m_instance; //静态成员变量 
};
pthread_once_t Singleton::ponce_ = PTHREAD_ONCE_INIT;
Singleton* Singleton::m_instance=nullptr; //静态成员需要先初始化

In 6 - [c ++ 11 version of the most simple cross-platform solutions]

Achieve four programs a little trouble, the program can not achieve five cross-platform. In fact, c ++ 11 already provides std :: call_once way to ensure the function is called only once in a multithreaded environment, the same, he also needs a once_flag of parameters. Usage and pthread_once similar, and cross-platform support.

In fact, there is one of the most simple solution!

Not only local static variable is initialized only once, but also thread-safe.

class Singleton{
public:
    // 注意返回的是引用。
    static Singleton& getInstance(){
        static Singleton m_instance;  //局部静态变量
        return m_instance;
    }
private:
    Singleton(); //私有构造函数,不允许使用者自己生成对象
    Singleton(const Singleton& other);
};

This embodiment is referred to as single-Meyers' Singleton. This method is very simple, and very perfect, but note:

  • After the gcc 4.0 compiler support such an approach.
  • Under C ++ 11 and later versions (such as C ++ 14) multi-threaded correctly.
  • C ++ is not so written before 11.

Guess you like

Origin www.cnblogs.com/Hijack-you/p/12013625.html