【C++】单例模式

单例模式:

一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理

饿汉模式

就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象

为了防止可以在类外随意创建对象,我们需要将构造函数私有化

此时我们需要一个接口来访问类,程序启动时就创建一个唯一的实例对象,所以我们需要使用静态变量指针来指向一个new 出来的类对象

而这个静态指针需要在类内声明,类外定义.因为这个_ins是私有静态,我们必须定义一个静态共有函数来访问该指针

此时我们可以通过GetInstanse函数获得这个指向这个类的指针来访问这个类,然后我们可以增加一个变量vector< string > _ret,来往这个对象里面增加数据,此时我们还需要写一个添加函数来往_ret中加数据,然后增加一个print函数来打印_ret;

在插入数据或者打印时候可能会有多个线程同时访问,所以必须加锁

饿汉模式

c 复制代码
#include<iostream>
#include<vector>
#include<string>
#include<thread>
#include<mutex>
using namespace std;
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		return _ins;

	}
	void add(string str)
	{   _mux.lock();
		_ret.push_back(str);
		_mux.unlock();
	}
	void print()
	{
		_mux.lock();
		for (auto& e : _ret)
		{
			cout << e << endl;

		}
		_mux.unlock();

	}
private:
	Singleton()
	{
	}


private:
	static Singleton* _ins;
	vector<string> _ret;
	mutex _mux;
};
Singleton* Singleton::_ins = new Singleton;
int main()
{
	//Singleton st;
	//static Singleton sr;
	Singleton::GetInstance()->add("张三");
	Singleton::GetInstance()->add("李四");
	Singleton::GetInstance()->add("王五");
	Singleton::GetInstance()->add("赵六");
    Singleton::GetInstance()->print();

}

懒汉模式

如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。

因为懒汉模式需要使用时创建对象,所以我们直接修改饿汉模式为懒汉模式

懒汉模式是使用时才初始化,所以类外定义为空,调用GetIntance()函数时,则是使用,第一次使用_ins为nullptr,所以第一次创建新对象,第二次或第三次调用,则_ins不为nullptr,所以直接return _ins.这里懒汉模式还有一下问题放后面说。

分析懒汉饿汉的优缺点

饿汉的缺点:

1.如果单例对象很大,main函数之前就要申请,第一,暂时不需要使用单会占用资源,第二程序启动会受影响.

2.如果两个单例都是饿汉,并且有依赖关系,要求单例1先创建,单例2在创建,饿汉模式无法控制顺序,而懒汉模式可以

饿汉的优点:

简单

修改懒汉模式

懒汉模式在GetInstance()的时候_ins属于临界资源,多线程来访问的话,会出现线程互斥,所以我们需要使用锁,由于GetInstance()是static,所以只能使用static 锁来加锁保护

这里还有一个问题,就是当线程都来GetInstance时,,都先要拿锁,之后在判断是否是第一次访问类,这样频繁拿锁会造成效率问题,这里饿汉模式是不会出现线程问题的,因为类的创建在main函数前就已经创建,而创建线程在main函数里面.

做出修改:

我们创建两个线程来往_ret中插入数据。

两个线程往_ret中插入60个数据.

懒汉模式类的析构

_imux锁是保证类只能被一个线程创建和释放的,保证线程安全,而_mux是保证_ret被一个线程访问

但是我们可能会忘记释放_ins,也就是忘记调用delinstance,一般全局都要使用单例对象,所以单例对象一般不需要显示释放,_ins为static所以delinstance也是static.

这里我们可以使用RAII思想,定义一个内部类,在该内部类中析构函数调用delinstance,

懒汉模式:

c 复制代码
#include<iostream>
#include<vector>
#include<string>
#include<thread>
#include<mutex>
#include<ctime>

using namespace std;
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (_ins == nullptr)
		{

			_imux.lock();
			if (_ins == nullptr)
			{
				_ins = new Singleton;
			}
			_imux.unlock();
		}
		return _ins;
		
	}
	static void delinstance()
	{
	  
		_imux.lock();
		if (_ins != nullptr)
		{

			delete _ins;
			_ins = nullptr;

		}
		_imux.unlock();

	}
	class GC
	{
	public:
		~GC()
		{
			delinstance();
		
		}
	};
    static GC _gc;



	void add(string str)
	{
		_mux.lock();
		_ret.push_back(str);
		_mux.unlock();
	}
	void print()
	{
		_mux.lock();
		for (auto& e : _ret)
		{
			cout << e << endl;

		}
		_mux.unlock();

	}
private:
	Singleton()
	{
	}


private:
	static Singleton* _ins;
	vector<string> _ret;
	mutex _mux;
	static mutex _imux;
};
Singleton* Singleton::_ins =nullptr;
mutex Singleton::_imux;
Singleton::GC Singleton::_gc;
int main()
{
	/*
	Singleton::GetInstance()->add("张三");
	Singleton::GetInstance()->add("李四");
	Singleton::GetInstance()->add("王五");
	Singleton::GetInstance()->add("赵六");
	Singleton::GetInstance()->print();*/
	
	int n = 30;
	thread n1([n]()
		{
			for (int i = 0; i < n; i++)
			{

				Singleton::GetInstance()->add("线程n1:" + to_string(rand()));
			}

       });
    thread n2([n]()
		{
			for (int i = 0; i < n; i++)
			{
             Singleton::GetInstance()->add("线程n2:" + to_string(rand()));
			}

		});
      n1.join();
	  n2.join();
	  Singleton::GetInstance()->print();
}

然后要注意的是不管懒汉还是饿汉模式都需要将拷贝构造和拷贝赋值给🈲用掉,c++98将拷贝构造和拷贝赋值定位私有,c++11后面加=delete;

这两个因为有锁,锁是不支持拷贝的,所以这里不会出现对象的拷贝构造的。


除了上述方式创建懒汉,还可以使用下面方法

c 复制代码
#include<iostream>
#include<vector>
#include<string>
#include<thread>
#include<mutex>
#include<ctime>

using namespace std;
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		static Singleton st;
		return &st;

	}
	void add(string str)
	{
		_mux.lock();
		_ret.push_back(str);
		_mux.unlock();
	}
	void print()
	{
		_mux.lock();
		for (auto& e : _ret)
		{
			cout << e << endl;

		}
		_mux.unlock();

	}
	Singleton(const Singleton& s) = delete;
	Singleton operator=(const Singleton& s) = delete;
private:
	Singleton()
	{
	}
	//Singleton(const Singleton& s);
	//Singleton operator=(const Singleton& s);


private:
	static Singleton* _ins;
	vector<string> _ret;
	mutex _mux;
	
};
Singleton* Singleton::_ins = nullptr;

这种方法也是可以的,但是这里有个问题,就是会出现线程安全问题。

C++11之前,这里不能保证初始化静态对象的线程安全问题

C++11之后,这里可以保证初始化静态对象的线程安全问题

懒汉模式封装线程池

c 复制代码
#include<iostream>
#include<pthread.h>
#include<vector>
#include<string>
#include<queue>
#include"task.hpp"
using namespace std;
struct threadinfo
{
  
  string name;
  pthread_t tid;
};
static int num=5;

template<class T>
class pthreadpool
{
public:
void lock()
{

pthread_mutex_lock(&mutex_);

}
void unlock()
{

pthread_mutex_unlock(&mutex_);
}
void cond_sleep()
{

pthread_cond_wait(&cond_,&mutex_);

}
void wakeup()
{

pthread_cond_signal(&cond_);

}
bool isempty()
{
  return tasks_.empty();
}



public:

static void* fun(void*args)  //线程执行函数里面处理线程池里的任务
{
   pthreadpool<T>*tp=static_cast<pthreadpool<T>*>(args);
   
 while(1)
    {  tp->lock();
     while(tp->isempty())
       {
         tp->cond_sleep();
       }
      
       T t=tp->Pop();
       tp->unlock();
        t();

    //cout<<t.getresult()<<endl;

   
    }

}

void start()
{
  int sz=threads_.size();
  for(int i=0;i<sz;i++)
    {
     threads_[i].name="thread-"+to_string(i+1);
     pthread_create(&(threads_[i].tid),nullptr,fun,this);
    }
}

void Push(const T& t)
{
  lock();
  tasks_.push(t);
  wakeup();
  unlock();
}
T Pop()
{
  T t=tasks_.front();
  tasks_.pop();
  return t;

}
  static pthreadpool<T>* getinstance()
   {
      if(nullptr==tp_)
      {
       pthread_mutex_lock(&lock_);
       if(nullptr==tp_)
          {
            tp_=new pthreadpool<T>();

          }
       
     pthread_mutex_unlock(&lock_);
      }

       return tp_;
  
   }







private:
pthreadpool(int defaultnum=num)
:threads_(defaultnum)
{
  pthread_mutex_init(&mutex_,nullptr);
  pthread_cond_init(&cond_,nullptr);
}
~pthreadpool()
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}

private:

vector<threadinfo>threads_;
queue<T>tasks_;


pthread_mutex_t mutex_;
pthread_cond_t cond_;
static pthreadpool<T>*tp_;
static pthread_mutex_t lock_;


};
template<class T>
pthreadpool<T> *pthreadpool<T>::tp_=nullptr;
template<class T>
pthread_mutex_t pthreadpool<T>::lock_=PTHREAD_MUTEX_INITIALIZER;
相关推荐
湫兮之风13 分钟前
C++:.front()函数作用
开发语言·c++
小老鼠不吃猫14 分钟前
力学笃行(四)Qt 线程与信号槽
c++·qt·信息可视化
小羊子说14 分钟前
Android 开发中 C++ 和Java 日志调试
android·java·c++
TechQuester19 分钟前
解决GPT-4o耗电难题!DeepMind新算法训练效率提升13倍,能耗降低10倍!
java·c++·人工智能·python·算法·chatgpt
观鉴词recommend35 分钟前
【c++刷题笔记-动态规划】day32: 509. 斐波那契数 、 70. 爬楼梯 、 746. 使用最小花费爬楼梯
c++·笔记·算法·leetcode·动态规划
DieSnowK35 分钟前
[C++][ProtoBuf][初识ProtoBuf]详细讲解
开发语言·c++·google·协议·序列化·反序列化·protobuf
酷酷学!!!1 小时前
C++第一弹 -- C++基础语法上(命名空间 输入输出 缺省参数 函数重载 引用)
开发语言·c++·学习方法·visual studio
郝YH是人间理想1 小时前
《算法笔记》总结No.3——排序
c语言·数据结构·c++·算法·排序算法·csp
semicolon_hello1 小时前
使用C++编写TCP服务端程序
服务器·网络·c++·tcp/ip
L小李要学习2 小时前
十一、作业
c语言·开发语言·c++