Резюме одноэлементного шаблона C++ для многопоточности

C++
Резюме одноэлементного шаблона C++ для многопоточности

Что такое потокобезопасность?

в владенииобщие данныеизПараллельное выполнение нескольких потоковВ программе будет проходить код безопасности потокамеханизм синхронизациигарантироватьКаждая нитьВсе может быть выполнено нормально и правильно, и не будет непредвиденных ситуаций, таких как загрязнение данных.


Как обеспечить потокобезопасность?

  1. даватьобщие ресурсыплюсЗамокЧтобы гарантировать, что каждая переменная ресурса все время занята потоком.
  2. Позвольте потокам также владеть ресурсами, не разделяя ресурсы в процессе. Например: использоватьthreadlocalОдин может поддерживаться для каждого потокачастныйлокальные переменные.

Что такое одноэлементный шаблон?

Одноэлементный шаблон гарантирует, что только один экземпляр класса может быть сгенерирован в течение всего жизненного цикла системы, гарантируя, что классУникальность.

Классификация одноэлементных паттернов

Шаблон Singleton можно разделить наленивыйиголодный китаец, разница между ними в том, чтоЭкземпляры создаются в разное время:

  • ленивый: Относится к работающей системе, экземпляр не существует, только когда экземпляр необходимо использовать, будет создан и использован экземпляр.(этот способ учитывать безопасность потоков)
  • голодный китаец: относится к инициализации и созданию экземпляра, как только система запускается, и может вызываться напрямую, когда это необходимо.(Сам потокобезопасен, проблем с многопоточностью нет)

Возможности класса Singleton

  • Конструкторы и деструкторыprivateтип, цельзапретитьВнешний конструктор и деструктор
  • Конструктор копирования и конструктор присваиванияprivateТипы цельзапретитьВнешнее копирование и назначение для обеспечения уникальности экземпляра
  • Есть класс, который получает экземплярстатическая функция, к которым можно получить глобальный доступ

01 Обычный ленивый синглтон (небезопасный поток)

///////////////////  普通懒汉式实现 -- 线程不安全 //////////////////
#include <iostream> // std::cout
#include <mutex>    // std::mutex
#include <pthread.h> // pthread_create

class SingleInstance
{

public:
    // 获取单例对象
    static SingleInstance *GetInstance();

    // 释放单例,进程退出时调用
    static void deleteInstance();
	
	// 打印单例地址
    void Print();

private:
	// 将其构造和析构成为私有的, 禁止外部构造和析构
    SingleInstance();
    ~SingleInstance();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator=(const SingleInstance &signal);

private:
    // 唯一单例对象指针
    static SingleInstance *m_SingleInstance;
};

//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;

SingleInstance* SingleInstance::GetInstance()
{

	if (m_SingleInstance == NULL)
	{
	    m_SingleInstance = new (std::nothrow) SingleInstance;  // 没有加锁是线程不安全的,当线程并发时会创建多个实例
	}

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = NULL;
    }
}

void SingleInstance::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleInstance::SingleInstance()
{
    std::cout << "构造函数" << std::endl;
}

SingleInstance::~SingleInstance()
{
    std::cout << "析构函数" << std::endl;
}
///////////////////  普通懒汉式实现 -- 线程不安全  //////////////////

// 线程函数
void *PrintHello(void *threadid)
{
    // 主线程与子线程分离,两者相互不干涉,子线程结束同时子线程的资源自动回收
    pthread_detach(pthread_self());

    // 对传入的参数进行强制类型转换,由无类型指针变为整形数指针,然后再读取
    int tid = *((int *)threadid);

    std::cout << "Hi, 我是线程 ID:[" << tid << "]" << std::endl;

    // 打印实例地址
    SingleInstance::GetInstance()->Print();

    pthread_exit(NULL);
}

#define NUM_THREADS 5 // 线程个数

int main(void)
{
    pthread_t threads[NUM_THREADS] = {0};
    int indexes[NUM_THREADS] = {0}; // 用数组来保存i的值

    int ret = 0;
    int i = 0;

    std::cout << "main() : 开始 ... " << std::endl;

    for (i = 0; i < NUM_THREADS; i++)
    {
        std::cout << "main() : 创建线程:[" << i << "]" << std::endl;
        
        indexes[i] = i; //先保存i的值
		
        // 传入的时候必须强制转换为void* 类型,即无类型指针
        ret = pthread_create(&threads[i], NULL, PrintHello, (void *)&(indexes[i]));
        if (ret)
        {
            std::cout << "Error:无法创建线程," << ret << std::endl;
            exit(-1);
        }
    }

    // 手动释放单实例的资源
    SingleInstance::deleteInstance();
    std::cout << "main() : 结束! " << std::endl;
	
    return 0;
}

Результат работы общего ленивого синглтона:

Как видно из бегущих результатов, конструктор синглтона создал два, а адреса памяти0x7f3c980008c0и0x7f3c900008c0, поэтому обычный ленивый синглтон подходит только для одного процесса и не подходит для многопоточности, потому что он небезопасен для потоков.


02 Заблокированный ленивый синглтон (потокобезопасность)

///////////////////  加锁的懒汉式实现  //////////////////
class SingleInstance
{

public:
    // 获取单实例对象
    static SingleInstance *&GetInstance();

    //释放单实例,进程退出时调用
    static void deleteInstance();
	
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    SingleInstance();
    ~SingleInstance();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    SingleInstance(const SingleInstance &signal);
    const SingleInstance &operator=(const SingleInstance &signal);

private:
    // 唯一单实例对象指针
    static SingleInstance *m_SingleInstance;
    static std::mutex m_Mutex;
};

//初始化静态成员变量
SingleInstance *SingleInstance::m_SingleInstance = NULL;
std::mutex SingleInstance::m_Mutex;

SingleInstance *&SingleInstance::GetInstance()
{

    //  这里使用了两个 if判断语句的技术称为双检锁;好处是,只有判断指针为空的时候才加锁,
    //  避免每次调用 GetInstance的方法都加锁,锁的开销毕竟还是有点大的。
    if (m_SingleInstance == NULL) 
    {
        std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
        if (m_SingleInstance == NULL)
        {
            m_SingleInstance = new (std::nothrow) SingleInstance;
        }
    }

    return m_SingleInstance;
}

void SingleInstance::deleteInstance()
{
    std::unique_lock<std::mutex> lock(m_Mutex); // 加锁
    if (m_SingleInstance)
    {
        delete m_SingleInstance;
        m_SingleInstance = NULL;
    }
}

void SingleInstance::Print()
{
	std::cout << "我的实例内存地址是:" << this << std::endl;
}

SingleInstance::SingleInstance()
{
    std::cout << "构造函数" << std::endl;
}

SingleInstance::~SingleInstance()
{
    std::cout << "析构函数" << std::endl;
}
///////////////////  加锁的懒汉式实现  //////////////////

Результат запуска заблокированного ленивого синглтона:

Как видно из бегущих результатов, создается только один экземпляр, а адрес памяти0x7f28b00008c0, так что обычный ленивый стиль с мьютексом является потокобезопасным


03 ленивых одноэлементных внутренних статических переменных (C++ 11 потокобезопасный)

///////////////////  内部静态变量的懒汉实现  //////////////////
class Single
{

public:
    // 获取单实例对象
    static Single &GetInstance();
	
	// 打印实例地址
    void Print();

private:
    // 禁止外部构造
    Single();

    // 禁止外部析构
    ~Single();

    // 禁止外部复制构造
    Single(const Single &signal);

    // 禁止外部赋值操作
    const Single &operator=(const Single &signal);
};

Single &Single::GetInstance()
{
    // 局部静态特性的方式实现单实例
    static Single signal;
    return signal;
}

void Single::Print()
{
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

Single::Single()
{
    std::cout << "构造函数" << std::endl;
}

Single::~Single()
{
    std::cout << "析构函数" << std::endl;
}
///////////////////  内部静态变量的懒汉实现  //////////////////

Результат запуска ленивого синглтона с внутренними статическими переменными:

-std=c++0xКомпиляция использует возможности C++ 11. Она является потокобезопасной в отношении внутренних статических переменных в C++ 11. Экземпляр создается только один раз, а адрес памяти0x6016e8, этот метод настоятельно рекомендуется и требует наименьшего количества кода для реализации!

[root@lincoding singleInstall]#g++  SingleInstance.cpp -o SingleInstance -lpthread -std=c++0x


04 Hungry singleton (сам по себе потокобезопасный)

////////////////////////// 饿汉实现 /////////////////////
class Singleton
{
public:
    // 获取单实例
    static Singleton* GetInstance();

    // 释放单实例,进程退出时调用
    static void deleteInstance();
    
    // 打印实例地址
    void Print();

private:
    // 将其构造和析构成为私有的, 禁止外部构造和析构
    Singleton();
    ~Singleton();

    // 将其拷贝构造和赋值构造成为私有函数, 禁止外部拷贝和赋值
    Singleton(const Singleton &signal);
    const Singleton &operator=(const Singleton &signal);

private:
    // 唯一单实例对象指针
    static Singleton *g_pSingleton;
};

// 代码一运行就初始化创建实例 ,本身就线程安全
Singleton* Singleton::g_pSingleton = new (std::nothrow) Singleton;

Singleton* Singleton::GetInstance()
{
    return g_pSingleton;
}

void Singleton::deleteInstance()
{
    if (g_pSingleton)
    {
        delete g_pSingleton;
        g_pSingleton = NULL;
    }
}

void Singleton::Print()
{
    std::cout << "我的实例内存地址是:" << this << std::endl;
}

Singleton::Singleton()
{
    std::cout << "构造函数" << std::endl;
}

Singleton::~Singleton()
{
    std::cout << "析构函数" << std::endl;
}
////////////////////////// 饿汉实现 /////////////////////

Текущий результат голодного синглтона:

Из результатов выполнения видно, что конструктор в стиле Hunger инициализируется в начале программы, поэтому сам по себе является потокобезопасным.


Особенности и опции

  • Ленивый стиль заключается в обмене времени на пространство, которое подходит для сравнения количества посещений.маленькийвремя; рекомендуетсяЛенивый синглтон для внутренних статических переменных, меньше кода
  • Голодный китайский стиль заключается в обмене пространства на время, что подходит для сравнения количества посещений.Большойвремя, или когда есть много потоков