C++之跨平台单例模式singleton

单例模式是一种常见的软件设计模式。在它的核心结构中只包含一个被称为 单例类的特殊类,通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问。

应用场景:对于系统中的某些类来说,只有一个实例很重要,例如一个系统中可以存在多个打印进程,但是只能有一个正在工作的打印进程;

根据维基百科对单例模式的描述:

确保一个类只有一个实例,并提供对该实例的全局访问

那么,我们可以从中得到一个单例模式很重要的特征:

一个类只有一个对象。

一、单线程模型:

对于一个普通的类,我们可以通过构造函数随意生成N个对象,显然为了使一个类只能有一个对象,那么我们第一步应该先把构造函数设置为私有函数,禁用其复制与赋值能力。于是有了以下的代码:

class Singleton
{
    private:
        Singleton(){ };
};

这样我们在main函数中就不能生成任何Singleton对象;

Singleton s;//error

那么我们该怎么解决这一问题呢?

这里我们根据static关键字的性质,绕过构造函数为private的限制,于是代码如下;

class Singleton
{
    public:
        static Singleton *getInstance()//static 静态存储区
        { return new Singleton; }  //new 类型

    private:
        Singleton(){ };
};

在main函数中这样调用:

int main(int argc, const char *argv[])
{
    cout << Singleton::getInstance() << endl;
    cout << Singleton::getInstance() << endl;
    return 0;
}

我们发现打印出的两个对象的首地址是不一样的,也就是说,两次Singleton::getInstance()函数的返回值是不同的,因为,这个函数每次为为我们new一个新的对象。
以上的代码产生的问题是 无法保证产生的对象唯一。

那么我们又该如何解决这一问题呢?

在函数成员中我们设置一个static指针,其初始值为空,自然这个指针为本类所拥有,不属于某一个具体对象,通过每次判断该指针是否为空,说明是否已经产生了一个对象。代码如下:

//多线程下有隐患
class Singleton
{
    public:
        static Singleton *getInstance()//static 静态存储区
        {
            if(pSingleton == NULL)
                 pSingleton = new Singleton; //new 类型
            return pSingleton;
        }  

    private:
        Singleton(){ };
        static Singleton* pSingleton;
};
Singleton *Singleton::pSingleton = NULL;//静态成员函数的定义

这样我们再用上面的main函数测试本段代码,结果说明值产生了一个对象。这说明我们实现了一个简单的单例模式。

二、多线程下的单例模式:

上段代码是否真的没有问题了呢?

显示不是的,我们考虑一个进程中含有多个线程的情形,代码如下:

#include <iostream>
#include "Thread.h"
#include <stdlib.h>
using namespace std;
//多线程隐患
class Singleton
{
    public:
        static Singleton *getInstance()//static 静态存储区
        {
            if(pSingleton == NULL)
            {
                ::sleep(1); //以防止内核速度过快
                pSingleton = new Singleton; //new 类型
            }
            return pSingleton;
        }  

    private:
        Singleton(){ }
        static Singleton* pSingleton;
};
Singleton *Singleton::pSingleton = NULL;

class MyThread:public Thread
{
    public:
        void run()
        {
            cout << Singleton::getInstance() << endl;
            cout << Singleton::getInstance() << endl;
        }
};
//本例测试了多线程隐患
int main(int argc, const char *argv[])
{
    //Singleton();
    const int Ksize = 12;
    MyThread threads[Ksize];

    int i ;
    for ( i = 0; i != Ksize; i++)
    {
        threads[i].start();
    }

    for ( i = 0; i != Ksize; i++)
    {
        threads[i].join();
    }
    cout <<"hello"<< endl;
    return 0;
}

测试结果如下:

0xb1300468
0xb1300498
0x9f88728
0xb1300498
0xb1300478
0xb1300498
0xb1100488
0xb1300498
0xb1300488
0xb1300498
0xb1300498
0xb1300498
0x9f88738
0xb1300498
0x9f88748
0xb1300498
0xb1100478
0xb1300498
0xb1100498
0xb1300498
0xb1100468
0xb1300498
0xb11004a8
0xb11004a8

我们发现,地址有很多是不同的。那么以上问题是怎么产生的呢?

因为,上面的进程中不止有一个线程,当有多个线程启动时,假设代号为A/B;当线程 A调用run函数时会在执行if(pSingleton==NULL)语句后停留一秒,这时B也会执行到if(pSingleton==NULL)语句,紧接着B会判断pSingleton是否为空,这时由于A还未new一个对象,所以B会进入到if语句的函数体中,所以此时A,B各会new一个新对象。所以就产生了以上结果。

那么又该如何解决这一问题呢?

很自然的我们会想到引进互斥锁,这样,就形成了以下代码:

//其余代码均未改动
class Singleton
{
    public:
        static Singleton *getInstance()
        {
            mutex_.lock();
            if(pInstance_ == NULL)
            {
                ::sleep(1);
                pInstance_ = new Singleton;
            }
            mutex_.unlock();
            return pInstance_;
        }
    private:
        Singleton(){ }
        static Singleton *pInstance_;
        static MutexLock mutex_;
};

Singleton *Singleton::pInstance_ = NULL; //static成员的定义
MutexLock Singleton::mutex_; 

显然,通过测试,证明此方法是可行的。但是这里存在着一个很严重的问题:效率问题。
互斥锁会极大的降低系统的并发能力,因为每次调用都要加锁解锁操作。

于是我们写了以下测试:

class TestThread : public Thread
{
public:
    void run()
    {
        const int kCount = 1000 * 1000;
        for(int ix = 0; ix != kCount; ++ix)
        {
            Singleton::getInstance();
        }
    }
};

int main(int argc, char const *argv[])
{
    //Singleton s; ERROR

    int64_t startTime = getUTime();

    const int KSize = 100;
    TestThread threads[KSize];
    for(int ix = 0; ix != KSize; ++ix)
    {
        threads[ix].start();
    }

    for(int ix = 0; ix != KSize; ++ix)
    {
        threads[ix].join();
    }

    int64_t endTime = getUTime();

    int64_t diffTime = endTime - startTime;
    cout << "cost : " << diffTime / 1000 << " ms" << endl;

    return 0;
}
//测试时间
int64_t getUTime()
{
    struct timeval tv;
    ::memset(&tv, 0, sizeof tv);
    if(gettimeofday(&tv, NULL) == -1)
    {
        perror("gettimeofday");
        exit(EXIT_FAILURE);
    }
    int64_t current = tv.tv_usec;
    current += tv.tv_sec * 1000 * 1000;
    return current;
}

上面的代码中,我们开了 100 个线程,每个线程 调用1M次Singleton::getInstance()
测试结果如下:

cost : 6729 ms

什么方法可以解决这一问题?

采用双重锁模式

再次改进我们的代码:

class Singleton
{
public:
    static Singleton *getInstance()
    {
        if(pInstance_ == NULL)
        {
            mutex_.lock();
            if(pInstance_ == NULL) //线程的切换
                pInstance_ = new Singleton;
            mutex_.unlock();
        }

        return pInstance_;
    }
private:
    Singleton() { }

    static Singleton *pInstance_;
    static MutexLock mutex_;
};

可以看到我们在getInstance中采用了 双重检查模式,这样的好处是什么呢?

安全性:内部已经采用互斥锁,代码无论如何都是安全的。
效率问题:当产生一个实例之后,pInstance就已不为空,后面每个线程访问到最外面的if判断就直接返回,并没有执行后面的加锁解锁操作

我们再次测试,结果如下:

cost :  458  ms

我们看到仅仅多了三行代码,却带来了十几倍的效率提升!!

Thread.h&Thread.cpp可参考:Linux组件封装之三:Thread

MutexLock.h&MutexLock.cpp可参考:Linux组件封装之一:MUtexLock

时间: 2024-12-14 02:17:31

C++之跨平台单例模式singleton的相关文章

.Net 单例模式(Singleton)

每台计算机可以有若干个打印机,但只能有一个Printer Spooler, 以避免两个打印作业同时输出到打印机中.每台计算机可以有若干传真卡,但是只应该有一个软件负责管理传真卡,以避免出现两份传真作业同时传到传真卡中的情况.每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用. 问题描述: 单例模式 Singleton Pattern 问题解决: (1)单例模式简介: Singleton模式要求一个类有且仅有一个实例,并且提供了一个全局的访问点.这

单例模式——Singleton

模式分类: 从目的来看: 1.创建型(Creational)模式:负责对象创建. 2.结构型(Structural)模式:处理类于对象间的组合. 3.行为型(Behavioral)模式:类与对象交互中的职责分配. 从范围看: 1.类模式处理类于子类的静态关系. 2.对象模式处理对象间的动态关系. 动机 在软件系统中,经常有一些这样特殊的类,必须保证他们在系统中只存在一个实例,才能确保他们的逻辑正确性.以及良好的效率. 绕过常规的构造器,提供一种机制保证一个类只有一个实例. 意图 保证一个类仅有一

【白话设计模式四】单例模式(Singleton)

转自:https://my.oschina.net/xianggao/blog/616385 0 系列目录 白话设计模式 工厂模式 单例模式 [白话设计模式一]简单工厂模式(Simple Factory) [白话设计模式二]外观模式(Facade) [白话设计模式三]适配器模式(Adapter) [白话设计模式四]单例模式(Singleton) [白话设计模式五]工厂方法模式(Factory Method) [白话设计模式六]抽象工厂模式(Abstract Factory) [白话设计模式七]策

php设计模式——单例模式(Singleton)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 谷歌的Android设备 华为的Android设备 IOS只属于苹果公司 IOS只属于苹果公司 1 <?php 2 3 /* 4 * php

设计模式之——单例模式(Singleton)的常见应用场景(转):

单例模式(Singleton)也叫单态模式,是设计模式中最为简单的一种模式,甚至有些模式大师都不称其为模式,称其为一种实现技巧,因为设计模式讲究对象之间的关系的抽象,而单例模式只有自己一个对象,也因此有些设计大师并把把其称为设计模式之一. 这里又不具体讲如何实现单例模式和介绍其原理(因为这方便的已经有太多的好文章介绍了),如果对单例模式不了解的可以先看下:http://terrylee.cnblogs.com/archive/2005/12/09/293509.html . 好多没怎么使用过的人

Android - 单例模式(singleton)的使用

单例模式(singleton)的使用 本文地址:http://blog.csdn.net/caroline_wendy 单例(singleton)是特殊的Java类,在创建实例时,一个类仅允许创建一个实例. 应用能够在内存里存多久,单例就能存在多久,因此将对象列表保存在单例里可保持crime数据的一直存在, 不管activity.fragment及它们的生命周期发生什么变化. 要创建单例,需创建一个带有私有构造方法及get()方法类,其中get()方法返回实例. 如实例已存在,get()方法则直

二十四种设计模式:单例模式(Singleton Pattern)

单例模式(Singleton Pattern) 介绍保证一个类仅有一个实例,并提供一个访问它的全局访问点. 示例保证一个类仅有一个实例. Singleton using System; using System.Collections.Generic; using System.Text; namespace Pattern.Singleton { /// <summary> /// 泛型实现单例模式 /// </summary> /// <typeparam name=&q

Java 设计模式 单例模式(Singleton) [ 转载 ]

Java 设计模式 单例模式(Singleton) [ 转载 ] 转载请注明出处:http://cantellow.iteye.com/blog/838473 前言 懒汉:调用时才创建对象 饿汉:类初始化时就创建对象 第一种(懒汉,线程不安全): 1 public class Singleton { 2 private static Singleton instance; 3 private Singleton (){} 4 5 public static Singleton getInstan

Android设计模式——单例模式(Singleton)

二十三种设计模式分为三大类: 创建型模式,共五种:工厂方法模式.抽象工厂模式.单例模式.建造者模式.原型模式. 结构型模式,共七种:适配器模式.装饰器模式.代理模式.外观模式.桥接模式.组合模式.享元模式. 行为型模式,共十一种:策略模式.模板方法模式.观察者模式.迭代子模式.责任链模式.命令模式.备忘录模式.状态模式.访问者模式.中介者模式.解释器模式. 1 package com.example.main; 2 3 import android.app.Activity; 4 import