多线程 - 单例设计模式中的多线程
多线程 - 单例设计模式中的多线程
单例设计模式简概
设计模式简概
(详见设计模式相关的笔记)
略
单例模式例程
(详见设计模式相关的笔记)
在设计模式中,使用频率比较高
class Single
{
private:
    Single(){}
    static Single *m_instance;	// 静态成员变量
public:
    static Single * Instance()
    {
        if(m_instance == nullptr)
        {
            m_instance = new Single();	// new了
            static GarbageCollector gc;	// 创建回收类
        }
        return m_instance;
    };
    
    // 不写这个也行,一般单例对象的生命周期就是覆盖整个程序的,退出程序自动析构
    class GarbageCollector				// 内部类,负责释放前面的new
    {
        ~GarbageCollector()
        {
            if(Single::m_instance)
            {
                delete Single::m_instance;
                Single::m_instance = nullptr;
            }
        }
    };
}
Single *Single::m_instance = nullptr;	// 初始化为空
int main
{
	Single *single = Single::Instance();	// 使用单例对象
    return 0;
}- Q:为什么不能在单例类的析构中进行delete?
 - A:当析构Single对象时,此时
m_instance指针可能已经被释放了。如果此时析构函数中delete m_instance;,有可能会释放该内存两次?出错
还是因为自己delete自己会造成逻辑混乱?
其实一般情况下也不用手动析构,一般单例对象的生命周期就是覆盖整个程序的,退出程序自动析构 
单例设计模式共享数据问题的分析、解决
存在问题
(详见设计模式相关的笔记)
单例模式若需要多线程下起作用(多个线程共用一个单例),则需要Instance()函数互斥
版本1:线程非安全
(可能多个线程时能够创建不止一个单例,有几率出错,不安全)
(出错原因和改进方法详见设计模式单例模式的笔记)
- 存在问题 
- 在多线程中,例如AB两个线程
 - A执行完
if(判断单例是否存在)还没执行下一行时,B恰好执行到if那行 - 这就会产生两个单例
 
 - 建议 
- 单线程可以用
 
 
class Single
{
private:
    Single(){}
    static Single *m_instance;				// 静态成员变量
public:
    static Single * Instance()
    {
        if(m_instance == nullptr)
        {
            m_instance = new Single();		// new了
        }
        return m_instance;
    };
}
Single *Single::m_instance = nullptr;		// 初始化为空
// 线程入口函数
void mythread()
{
    cout << "子线程" << endl;
    Single *single = Single::Instance();	// 使用单例对象
    return;
}
int main
{
	std::thread mytobj(mythread);
    return 0;
}版本2:线程安全 - 加锁版(Cleck Lock)
- 存在问题: 
- 锁的代价过高
 - 读的时候根本没必要去锁,性能浪费
 
 - 建议 
- 可以用,但高并发的话性能损耗大
 
 
#include <mutex>
using namespace std;
std::mutex mutex1;							// 【互斥量】
class Single
{
private:
    Single(){}
    static Single *m_instance;				// 静态成员变量
public:
    static Single * Instance()
    {
        std::unique_lock<std::mutex> up_mutex(mutex1)	// 【互斥量加锁】
        if(m_instance == nullptr)
        {
            m_instance = new Single();		// new了
        }
        return m_instance;
    };
}
Single *Single::m_instance = nullptr;		// 初始化为空
// 线程入口函数
void mythread()
{
    cout << "子线程" << endl;
    Single *single = Single::Instance();	// 使用单例对象
    return;
}
int main
{
	std::thread mytobj(mythread);
    return 0;
}版本3:线程安全 - 双检查锁(Double Cleck Lock)
- 存在问题 
- 由于内存读写reorder不安全,会导致双检查锁失效
 - reorder:从汇编指令的角度来看,
m_instance = new Singleton();这行代码可以分解成三个步骤- (1) 分配内存
 - (2) 调用构造器
 - (3) 地址返回值给指针
 - 但这三步只是理想指令执行顺序,实际情况中有可能 被reorder(重排顺序),顺序变成了 1-3-2
 
 - 那么假设存在AB两个线程并发生以下情况 
- 当A线程执行
m_instance = new Singleton();时被reorder,即按1-3-2顺序执行指令 - 当A线程执行完指令3但还没执行指令2时
 - B恰好依次执行代码第2行的判断、第8行的return。但此时构造器还未被调用,会出错
 
 - 当A线程执行
 
 - 建议 
- 不要用,不安全,容易出错
 
 
版本4:C++11的 volatile 关键字
- 简概 
- C++11引入的新函数
 
 - 功能 
- 原理主要是让编译器不reorder
 
 
(函数略)
版本5:C++11的 std::call_once()
- 简概 
- C++11引入的新函数,感觉这个函数简直是为单例模式量身定做的
 
 - 功能 
- 能保证某个函数只被调用一次
 - 能实现互斥量的功能,且效率上会更高
 - 与if-else相比,在多线程中使用会更安全
 
 - 原理: 
- 第二个参数是需要调用的函数名
 - 第一个参数是一个
std::once_flag类型的标记。该标记将决定函数是否执行。
当执行过一次call_once()后,就会把这个标记设置为已调用状态。后续再调用时就会无法调用 
 
using namespace std;
std::once_flag g_flag;						// 【标记】这是个标记
class Single
{
private:
    Single(){}
    static Single *m_instance;				// 静态成员变量
    static void CreateInstance()			// 创建实例【只被调用一次】
    {
        m_instance = new Single;
    }
public:
    static Single * Instance()				// 使用实例
    {
        std:call_once(g_flage, CreateInstance);	// 【call_once方法】传入标记、函数名作参
        return m_instance;
    };
}
Single *Single::m_instance = nullptr;		// 初始化为空
// 线程入口函数
void mythread()
{
    cout << "子线程" << endl;
    Single *single = Single::Instance();	// 使用单例对象
    return;
}
int main
{
    Single::Instance();						// 【在主线程中初始化创建】那么new出来的对象就会在主线程的堆栈空间中,更好
	std::thread mytobj(mythread);
    return 0;
}版本6:直接使用static的简易版本
C++11中还有一种简单的单例,就是直接使用static。
C++11中可以保证static变量时多线程安全的,在底层实现了加锁操作,而且由于是static对象,也可以保证对象只生成一次
但用这种写法的基类可能因使用时操作不当不安全(比如不用static对象,自己又另外创建了一个)
链接到当前文件 0
没有文件链接到当前文件