购物类型网站建设wordpress购物车表单
web/
2025/10/4 14:49:40/
文章来源:
购物类型网站建设,wordpress购物车表单,wordpress支持支付宝吗,怎样做酒店网站ppt1.什么是单例模式 在一个项目中#xff0c;全局范围内#xff0c;某个类的实例有且仅有一个#xff0c;通过这个唯一实例向其他模块提供数据的全局访问#xff0c;这种模式就叫单例模式。 类中多对象的操作函数有如下几个#xff1a;
构造函数 #xff1a; 能够创建出一…1.什么是单例模式 在一个项目中全局范围内某个类的实例有且仅有一个通过这个唯一实例向其他模块提供数据的全局访问这种模式就叫单例模式。 类中多对象的操作函数有如下几个
构造函数 能够创建出一个新对象拷贝构造函数 能够根据一个已经存在的对象拷贝出一个新对象赋值操作符重载函数 用一个对象给另一个对象赋值
为了使得类全局只有一个实例我们需要对这些函数做一些处理
构造函数私有化且在类内部只被调用一次 由于使用者在类外部不能使用构造函数所以在类内部创建的这个唯一的对象必须是静态的这样就可以通过类名来访问了为了不破坏类的封装我们都会把这个静态对象的访问权限设置为 private。 类中只有它的静态成员函数才能访问其静态成员变量所以可以给这个单例类提供一个静态函数用于得到这个静态的实例对象。 拷贝构造函数 私有化 或者 禁用(private 或者 delete)赋值操作符重载函数私有化 或者 禁用。这个操作有没有都没影响
单例模式的代码模板
// 定义一个单例模式的类
class Singleton
{
public:// delete 代表函数禁用, 也可以将其访问权限设置为私有Singleton(const Singleton rhs) delete;Singleton operator(const Singleton rhs) delete;static Singleton* getInstance();
private:Singleton() default;static Singleton* m_obj;
};2.单例模式
单例模式可以分为 懒汉式 和 饿汉式。
一、懒汉式
饿汉模式就是在类加载的时候立刻进行实例化这样就得到了一个唯一的可用对象。
定义
// 饿汉模式 在调用 get_instance 之前 实例就已经存在了
// 多线程环境下 , 饿汉模式是线程 安全的
class TaskQueue {
public:TaskQueue(const TaskQueue rhs) delete;TaskQueue operator (const TaskQueue rhs) delete;static TaskQueue* get_instance() {return m_task_queue;}void print() {cout 我是单例对象 TaskQueue 的一个成员函数... endl;}private:TaskQueue() default;static TaskQueue* m_task_queue;
};TaskQueue* TaskQueue::m_task_queue new TaskQueue;int main()
{TaskQueue* task_queue TaskQueue::getInstance();task_queue-print();
}需要注意的是
在定义这个 TaskQueue 类的时候这个静态的单例对象 m_task_queue 就已经被创建出来了当调用 TaskQueue::get_instance() 的时候对象就已经被实例化了类中的静态成员变量需要在类外初始化饿汉式在多线程环境下是线程安全的
二、懒汉式
懒汉式是在类加载的时候不去创建这个唯一的实例而是在需要使用的时候再进行实例化。
定义
// 懒汉模式 在调用 get_instance 之前 实例存在 , 第一次调用 get_instance 才会实例化对象
// 多线程环境下, 饿汉模式是线程 不安全的
class TaskQueue {
public:TaskQueue(const TaskQueue rhs) delete;TaskQueue operator (const TaskQueue rhs) delete;static TaskQueue* get_instance() {if (m_task_queue nullptr) {//在第一次调用 get_instance() 的时候再初始化m_task_queue new TaskQueue;}return m_task_queue;}void print() {cout 我是单例对象 TaskQueue 的一个成员函数... endl;}private:TaskQueue() default;static TaskQueue* m_task_queue;
};TaskQueue* TaskQueue::m_task_queue nullptr;int main()
{TaskQueue* task_queue TaskQueue::getInstance();task_queue-print();
}上述代码在单线程环境下是没问题的。但是在多线程环境下就会出问题假设多个线程同时调用 get_instance() 函数并且此时 m_task_queue nullptr那么就可能创建出多个实例这就不符合单例模式的定义。
解决方案一加锁效率比较低
我们可以使用互斥锁 mutex 将创建实例的代码锁住第一次只有一个线程进来创建对象。
代码
// 用 双重检测锁定 解决懒汉式多线程环境下线程不安全的问题
class TaskQueue {
public:TaskQueue(const TaskQueue rhs) delete;TaskQueue operator (const TaskQueue rhs) delete;static TaskQueue* get_instance() {m_mutex.lock(); //加锁if (m_task_queue nullptr){m_task_queue new TaskQueue;}m_mutex.unlock(); //解锁return m_task_queue;}void print() {cout 我是单例对象 TaskQueue 的一个成员函数... endl;}private:TaskQueue() default;static TaskQueue* m_task_queue;static mutex m_mutex;
};TaskQueue* TaskQueue::m_task_queue nullptr;
mutex TaskQueue::m_mutex;上面代码虽然解决了问题但是 get_instance() 中的锁住的代码段每次就只有一个线程来访问这样效率就非常低。
解决方法二双重检测锁定存在问题
双重检测锁定的思路是在加锁和解锁代码块 之外再加一个 if 判断。这样的话在第一次调用 get_instance() 的线程仍然会阻塞第二次调用 get_instance() 的线程此时 m_task_queue 已经被实例化了也就是不为 nullptr 了那么第二次的线程在来到一个 if 判断的时候就直接退出了不需要再加锁解锁这样效率就提升了。
代码
// 用 双重检测锁定 解决懒汉式多线程环境下线程不安全的问题
class TaskQueue {
public:TaskQueue(const TaskQueue rhs) delete;TaskQueue operator (const TaskQueue rhs) delete;static TaskQueue* get_instance() {//外面再加一层判断if (m_task_queue nullptr) {m_mutex.lock();if (m_task_queue nullptr) {m_task_queue new TaskQueue;}m_mutex.unlock();}return m_task_queue;}void print() {cout 我是单例对象 TaskQueue 的一个成员函数... endl;}private:TaskQueue() default;static TaskQueue* m_task_queue;static mutex m_mutex;
};TaskQueue* TaskQueue::m_task_queue nullptr;
mutex TaskQueue::m_mutex;实际上 双重检测锁定 的代码还是有问题的。
假设此时有两个线程 A 和 B线程 A 刚好要调用 m_task_queue new TaskQueue; 这一句代码假设此时 m_task_queue nullptr而线程 B 刚好来到第一个 if 判断。 static TaskQueue* get_instance() {//线程B 马上进入下面这个 if 判断if (m_task_queue nullptr) {m_mutex.lock();if (m_task_queue nullptr) {//线程A 马上调用下面这一句代码m_task_queue new TaskQueue;}m_mutex.unlock();}return m_task_queue;}对于 m_task_queue new TaskQueue; 创建对象的这一句代码在底层实际上时会被分成三个步骤
第一步分配内存用于存储 TaskQueue 对象第二步在分配的内存中构造一个 TaskQueue 对象(初始化内存)第三步指针 m_task_queue 指向分配的内存
由于编译器底层对我们的代码进行优化就会将这些指令进行重排序也就是打乱了它本来的步骤。
比如说将上述的步骤重排序之后变成下面的
第一步分配内存用于存储 TaskQueue 对象第二步指针 m_task_queue 指向分配的内存第三步在分配的内存中构造一个 TaskQueue 对象(初始化内存)
即 第二步 和 第三步 颠倒了顺序。
指令重排序在单线程下没有问题在多线程下就有可能出现问题。
假设线程 A 此时刚好把前两步执行完了m_task_queue 此时已经指向一块内存了不过对这块内存进行操作是非法操作因为创建对象还没有完成线程 B 此时正好进入第一个 if 判断此时 m_task_queue 不为 nullptr就直接退出返回了没有构造完全的对象 m_task_queue。
如果线程 B 对这个对象进行操作就会出问题。
解决方法三双重检测锁定 原子变量 效率更低
C 11 引入了 原子变量 atomic 可以解决 双重检测锁定 的问题。
代码
// 用 原子变量 解决双重检测 的问题
class TaskQueue {
public:TaskQueue(const TaskQueue rhs) delete;TaskQueue operator (const TaskQueue rhs) delete;static TaskQueue* get_instance() {TaskQueue* task_queue m_task_queue.load();if (task_queue nullptr) {m_mutex.lock();task_queue m_task_queue.load();if (task_queue nullptr) {task_queue new TaskQueue;m_task_queue.store(task_queue);}m_mutex.unlock();}return task_queue;}void print() {cout 我是单例对象 TaskQueue 的一个成员函数... endl;}private:TaskQueue() default;//static TaskQueue* m_task_queue;static atomicTaskQueue* m_task_queue;static mutex m_mutex;
};//TaskQueue* TaskQueue::m_task_queue nullptr;
atomicTaskQueue* TaskQueue::m_task_queue;
mutex TaskQueue::m_mutex;上面代码中使用原子变量 atomic 的 store() 函数来存储单例对象使用 load() 函数来加载单例对象。
在原子变量中这两个函数在处理指令的时候默认的原子顺序是 memory_order_seq_cst即顺序原子操作 - sequentially consistent这样也就避免了之前的指令重排的问题使用顺序约束原子操作库整个函数执行都将保证顺序执行并且不会出现数据竞态data races缺点就是使用这种方法实现的懒汉模式的单例执行效率更低一些。
解决方法四静态局部变量推荐
在 C 11 直接使用 静态局部变量 在多线程环境下是不会出现问题的。
代码
class TaskQueue
{
public:// delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue rhs) delete;TaskQueue operator(const TaskQueue rhs) delete;static TaskQueue* getInstance(){static TaskQueue task_queue;return task_queue;}void print(){cout 我是单例对象 TaskQueue 的一个成员函数... endl;}private:TaskQueue() default;
};int main()
{TaskQueue* queue TaskQueue::getInstance();queue-print();return 0;
}之所以上面代码是线程安全的 是因为 C 11 规定了并且这个操作是在编译时由编译器保证的 如果指令逻辑进入一个未被初始化的声明变量所有并发执行应当等待该变量完成初始化。 三、总结
懒汉模式的缺点是在创建实例对象的时候有安全问题但这样可以减少内存的浪费如果用不到就不去申请内存了。饿汉模式则相反在我们不需要这个实例对象的时候它已经被创建出来占用了一块内存。
四、练习
实现一个 任务队列。生产者线程生产任务加入任务队列消费者线程取出任务队列的任务执行。
类成员
存储任务的容器我们直接使用 STL 中的容器 queue互斥锁(mutex)在多线程访问的情况下用于保护共享数据
成员函数
判断任务队列是否为空往任务队列中添加一个任务往任务队列总删除一个任务从任务队列中取出一个任务
为了简单起见我们用一个 int 数表示一个任务。
代码
#if 1
// 用局部静态变量饿汉式单例 实现任务队列class TaskQueue {
public:TaskQueue(const TaskQueue rhs) delete;TaskQueue operator (const TaskQueue rhs) delete;static TaskQueue* get_instance() {static TaskQueue task_queue;return task_queue;}//判断任务队列是否为空bool is_empty() {lock_guardmutex locker(m_mutex);return q.empty();}//删除任务bool delete_task() {lock_guardmutex locker(m_mutex);if (q.empty()) return false;q.pop();return true;}//取出任务 (不删除任务)int take_task() {lock_guardmutex locker(m_mutex);if (q.empty()) return -1;return q.front();}//添加任务void add_task(int task) {lock_guardmutex locker(m_mutex);q.push(task);}private:TaskQueue() default;queueint q;mutex m_mutex;
};#endifint main() {TaskQueue* task_queue TaskQueue::get_instance();thread t1([]() {//生产者 t1 给任务队列添加10个任务for (int i 0; i 10; i) {int task i 100;task_queue-add_task(task);cout producer thread produce a task : task , thread id is this_thread::get_id() endl;this_thread::sleep_for(chrono::milliseconds(500));}});thread t2([](){//让生产者线程先执行 保证先有任务this_thread::sleep_for(chrono::milliseconds(500));while (!task_queue-is_empty()) {int task task_queue-take_task();task_queue-delete_task();cout consumer thread consume a task : task , thread id is this_thread::get_id() endl;this_thread::sleep_for(chrono::milliseconds(1000));}});t1.join();t2.join();return 0;
}3.参考
本篇博客是对于 单例模式 的整理。
C 单例模式总结5种单例实现方法
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/86836.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!