单例模式实现及优化(C++11)

发布时间:2024年01月15日

单例模式

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

----引自《设计模式 可复用面向对象软件的基础》

实现

  • 类的构造函数私有化(private)
  • 类的实例创建由类自身完成
  • 类提供一个静态成员函数获取类的唯一实例

代码

未优化版本(mutex版本)

#include <iostream>
#include <mutex>

class Singleton
{
public:
    static Singleton* GetInstance() {
        std::lock_guard<std::mutex> lk(mutex_);
        if (instance_ == nullptr) {
            instance_ = new Singleton();
        }
        return instance_;
    }
    
    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }
private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }

    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}

优化版本1(call_once版本)

  • 将锁和互斥量优化为std::call_once
    std::call_once 是 C++ 标准库提供的一个函数,用于保证一个函数只会被调用一次,即使在多线程环境下也能确保线程安全,刚好符合我们实例只进行一次创建的需求。

call_once具备互斥量这种能力,而且效率上,比互斥量消耗的资源更少

#include <iostream>
#include <mutex> //std::call_once std::once_flag

class Singleton
{
public:
    static Singleton* GetInstance() {
        std::call_once(once_flag_, []() {
            instance_ = new Singleton();
        });
        
        return instance_;
    }
    
    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }
private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }
    
    static Singleton* instance_;
    static std::once_flag once_flag_;
};

Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::once_flag_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}

优化版本2 (dcl版本)

不管是互斥量版本,还是call_once版本,多线程环境下都会存在判断互斥量或once_flag的状态,本质上还是会造成性能开销,可以做一点优化,即双重检查锁(DCL)版本, 使用mutex的DCL版本如下(有问题版本,后面解释和优化):

#include <iostream>
#include <mutex>

class Singleton
{
public:
    static Singleton* GetInstance() {
        Singleton *tmp = instance_;
        /**********内存屏障***********/
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lk(mutex_);
            tmp = instance_;
            if (tmp == nullptr) {
                tmp = new Singleton();
                /**********内存屏障***********/
                instance_ = tmp;
            }
        }
        
        return instance_;
    }

    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }
private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }

    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}

双重检查锁只是将锁的粒度更细化

代码中的/*************内存屏障************/是必不可少的,否则可能存在某一个或多个现场获取的实例instance_是不完整的,或者叫未初始化完成的。原因是new Singleton包含分配地址空间,在地址空间上执行Singleton的构造函数,如果出现指令重排序,就会造成提前获取到Singleton实例,但Singleton构造函数还未真正执行。

到这里,也就是说明DCL是失效的,怎么解决呢?

The double-checked locking pattern (DCLP) is a bit of a notorious case study in lock-free programming. Up until 2004, there was no safe way to implement it in Java. Before C++11, there was no safe way to implement it in portable C++.

  • 1、针对Java,C#等语言,对instance_增加volatile修饰即可解决该问题,是语言提供的同步机制,禁止编译器和处理器进行重排序。
  • 2、C++11的volatile不适用于多线程环境的同步语境,没有上述的能力,仅仅具备标识该对象是特种内存(禁止编译器和处理器进行冗余加载,废弃存储的优化而已),比如IO映射内存。在C++11提供atomic的相关类型和操作可以解决DCL失效问题。
    • 2.1 使用顺序一致内存顺序原子类型【代码1】
    • 2.2 使用松散内存顺序原子类型+内存屏障【代码2】

【代码1】

#include <iostream>
#include <mutex>
#include <atomic>

class Singleton
{
public:
    static Singleton* GetInstance() {
        Singleton *tmp = instance_.load(); // 默认std::memory_order_seq_cst
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lk(mutex_);
            tmp = instance_.load();// 默认std::memory_order_seq_cst
            if (tmp == nullptr) {
                tmp = new Singleton();
                instance_.store(tmp);// 默认std::memory_order_seq_cst
            }
        }
        
        return tmp;
    }

    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }
private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }

    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_ {nullptr};
std::mutex Singleton::mutex_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}

【代码2】

#include <iostream>
#include <mutex>
#include <atomic>

class Singleton
{
public:
    static Singleton* GetInstance() {
        Singleton *tmp = instance_.load(std::memory_order_relaxed);
        std::atomic_thread_fence(std::memory_order_acquire);
        if (tmp == nullptr) {
            std::lock_guard<std::mutex> lk(mutex_);
            tmp = instance_.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton();
                std::atomic_thread_fence(std::memory_order_release);
                instance_.store(tmp, std::memory_order_relaxed);
            }
        }
        
        return tmp;
    }
    
    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }
private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }

    static std::atomic<Singleton*> instance_;
    static std::mutex mutex_;
};

std::atomic<Singleton*> Singleton::instance_ {nullptr};
std::mutex Singleton::mutex_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}

关于dclp模型相关资料请参考Double-Checked Locking is Fixed In C++11
关于c++11内存模型,内存顺序,原子类型请参考《C++并法编程实践》第5章(C++内存模型和原子类型上的操作)
关于volatile和原子类型的适用场景请参考《Effective Modern C++》(第 40 项:使用 std::atomic 实现并发,使用 volatile 实现特殊内存)

单例对象资源释放

到这里我们还需要考虑单例对象的资源释放问题:

  • 方案1: 使用RAII技巧,创建一个静态全局对象,进程退出时,由操作系统释放静态全局对象,有静态全局对象去释放单例对象资源。
  • 方案2: 使用c++11智能指针存储单例对象。

使用mutex版本实现:
方案1代码:

#include <iostream>
#include <mutex>
#include <memory>

class Singleton
{
public:
    static Singleton* GetInstance() {
        std::lock_guard<std::mutex> lk(mutex_);
        if (instance_ == nullptr) {
            instance_ = new Singleton();
        }
        return instance_;
    }

    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }

private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }

    class GC {
    public:
        GC() {
            std::cout << "GC construct" << std::endl;
        }

        ~GC() {
            std::cout << "GC destruct" << std::endl;
            if (instance_ != nullptr) {
                delete instance_;
                instance_ = nullptr;
                std::cout << "instance_ is deleted" << std::endl;
            }
        }
    };

    static GC gc;
    static Singleton* instance_;
    static std::mutex mutex_;
};

Singleton::GC Singleton::gc;
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}
GC construct
begin
Singleton construct
end
GC destruct
Singleton destruct
instance_ is deleted

方案2代码:

#include <iostream>
#include <mutex>
#include <memory>

class Singleton
{
public:
    static Singleton* GetInstance() {
        std::lock_guard<std::mutex> lk(mutex_);
        if (instance_ == nullptr) {
            instance_ = std::unique_ptr<Singleton>(new Singleton());
        }
        return instance_.get();
    }

    ~Singleton() {
        std::cout << "Singleton destruct" << std::endl;
    }

private:
    Singleton() {
        std::cout << "Singleton construct" << std::endl;
    }

    static std::unique_ptr<Singleton> instance_;
    static std::mutex mutex_;
};

std::unique_ptr<Singleton> Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

int main(void)
{
    std::cout << "begin" << std::endl;
    auto p = Singleton::GetInstance();
    std::cout << "end" << std::endl;
    return 0;
}
begin
Singleton construct
end
Singleton destruct
文章来源:https://blog.csdn.net/weixin_36623563/article/details/135555406
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。