Python-c++的23種設計模式- 1 單例模式

原文地址

1. Python單例模式

總線是計算機各種功能部件或者設備之間傳送數據岳锁、控制信號等信息的公共通信解決方案之一。
現假設有如下場景:某中央處理器(CPU)通過某種協(xié)議總線與一個信號燈相連哎甲,信號燈有64種顏色可以設置,中央處理器上運行著三個線程扑馁,都可以對這個信號燈進行控制蹲堂,并且可以獨立設置該信號燈的顏色。
抽象掉協(xié)議細節(jié)(用打印表示)或详,如何實現線程對信號等的控制邏輯系羞。
加線程鎖進行控制,無疑是最先想到的方法霸琴,但各個線程對鎖的控制椒振,無疑加大了模塊之間的耦合。下面梧乘,我們就用設計模式中的單例模式澎迎,來解決這個問題。
什么是單例模式选调?單例模式是指:保證一個類僅有一個實例夹供,并提供一個訪問它的全局訪問點。
具體到此例中仁堪,總線對象哮洽,就是一個單例,它僅有一個實例弦聂,各個線程對總線的訪問只有一個全局訪問點鸟辅,即惟一的實例。
Python代碼如下:

# encoding=utf8
import threading
import time
# 這里使用方法__new__來實現單例模式


class Singleton(object):  # 抽象單例
    def __new__(cls, *args, **kw):
        if not hasattr(cls, '_instance'):
            orig = super(Singleton, cls)
            cls._instance = orig.__new__(cls, *args, **kw)
        return cls._instance
# 總線


class Bus(Singleton):
    lock = threading.RLock()

    def sendData(self, data):
        self.lock.acquire()
        time.sleep(3)
        print("Sending Signal Data...", data)
        self.lock.release()
# 線程對象莺葫,為更加說明單例的含義剔桨,這里將Bus對象實例化寫在了run里


class VisitEntity(threading.Thread):
    my_bus = ""
    name = ""

    def getName(self):
        return self.name

    def setName(self, name):
        self.name = name

    def run(self):
        self.my_bus = Bus()
        print("VisitEntity.run(), self.my_bus %s " % self.my_bus)
        self.my_bus.sendData(self.name)


if __name__ == "__main__":
    for i in range(3):
        print("Entity %d begin to run..." % i)
        my_entity = VisitEntity()
        my_entity.setName("Entity_"+str(i))
        my_entity.start()

運行結果如下:

Entity 0 begin to run...
Entity 1 begin to run...
Entity 2 begin to run...
Sending Signal Data... Entity_0
Sending Signal Data... Entity_1
Sending Signal Data... Entity_2

在程序運行過程中,三個線程同時運行(運行結果的前三行先很快打印出來)徙融,而后分別占用總線資源(后三行每隔3秒打印一行)洒缀。雖然看上去總線Bus被實例化了三次,但實際上在內存里只有一個實例。

二树绩、單例模式

單例模式是所有設計模式中比較簡單的一類萨脑,其定義如下:Ensure a class has only one instance, and provide a global point of access to it.(保證某一個類只有一個實例,而且在全局只有一個訪問點)

圖片.png

三饺饭、單例模式的優(yōu)點和應用

  • 單例模式的優(yōu)點
    1渤早、由于單例模式要求在全局內只有一個實例,因而可以節(jié)省比較多的內存空間瘫俊;
    2鹊杖、全局只有一個接入點,可以更好地進行數據同步控制扛芽,避免多重占用骂蓖;
    3、單例可長駐內存川尖,減少系統(tǒng)開銷登下。

  • 單例模式的應用舉例
    1、生成全局惟一的序列號叮喳;
    2被芳、訪問全局復用的惟一資源,如磁盤馍悟、總線等畔濒;
    3、單個對象占用的資源過多锣咒,如數據庫等篓冲;
    4、系統(tǒng)全局統(tǒng)一管理宠哄,如Windows下的Task Manager;
    5嗤攻、網站計數器毛嫉。

四、單例模式的缺點

1妇菱、單例模式的擴展是比較困難的承粤;
2、賦于了單例以太多的職責闯团,某種程度上違反單一職責原則(六大原則后面會講到);
3辛臊、單例模式是并發(fā)協(xié)作軟件模塊中需要最先完成的,因而其不利于測試房交;
4彻舰、單例模式在某種情況下會導致“資源瓶頸”。

2. C++ 單例模式:

什么是單例模式

單例模式是一種對象創(chuàng)建型模式,使用單例模式刃唤,可以保證為一個類只生成唯一的實例對象隔心。也就是說,在整個程序空間中尚胞,該類只存在一個實例對象硬霍。

為什么使用單例模式

在應用系統(tǒng)開發(fā)中,我們常常有以下需求:

1.需要生成唯一序列的環(huán)境
2.需要頻繁實例化然后銷毀的對象笼裳。
3.創(chuàng)建對象時耗時過多或者耗資源過多唯卖,但又經常用到的對象。
4.方便資源相互通信的環(huán)境

  1. 其他躬柬。拜轨。。

實際案例:

多線程中網絡資源初始化
回收站機制
任務管理器
應用程序日志管理楔脯。

單例模式實現步驟

構造函數私有化提供一個全局的靜態(tài)方法撩轰,訪問唯一對象類中定義一個靜態(tài)指針,指向唯一對象昧廷。
圖片.png

單例模式實現代碼

  • 懶漢式
#include <iostream>
using namespace std;
//懶漢式
class SingleTon 
{
private:
    SingleTon();
public:
    static SingleTon* m_singleTon;
    static SingleTon* GetInstance();
    void TestPrint();
};
//懶漢式并沒有創(chuàng)建單例對象
SingleTon* SingleTon::m_singleTon = NULL;       
int main()
{
    SingleTon* p1 = SingleTon::GetInstance();
    SingleTon* p2 = SingleTon::GetInstance();
    cout << "p1:"<<  hex << p1 << endl;
    cout << "p2:" << hex << p2 << endl;
    p1->TestPrint();
    p2->TestPrint();
    return 0;
}
SingleTon::SingleTon()
{
    m_singleTon = NULL;
    cout << "構造了對象....." << endl;
}
SingleTon* SingleTon::GetInstance()
{
    if (m_singleTon == NULL) 
    {
        m_singleTon = new SingleTon;
    }
    return m_singleTon;
}
void SingleTon::TestPrint()
{
    cout << "測試調用....." << endl;
}
  • 餓漢式
#include <iostream>
using namespace std;
//懶漢式
class SingleTon 
{
private:
    SingleTon();
public:
    static SingleTon* m_singleTon;
    static SingleTon* GetInstance();
    void TestPrint();
};
//餓漢式創(chuàng)建單例對象
SingleTon* SingleTon::m_singleTon = new SingleTon;      
int main()
{
    SingleTon* p1 = SingleTon::GetInstance();
    SingleTon* p2 = SingleTon::GetInstance();
    cout << "p1:"<<  hex << p1 << endl;
    cout << "p2:" << hex << p2 << endl;
    p1->TestPrint();
    p2->TestPrint();
    return 0;
}
SingleTon::SingleTon()
{
    m_singleTon = NULL;
    cout << "構造了對象....." << endl;
}
SingleTon* SingleTon::GetInstance()
{
    return m_singleTon;
}
void SingleTon::TestPrint()
{
    cout << "測試調用....." << endl;
}

單例模式優(yōu)缺點

  • 優(yōu)點:
    在內存中只有一個對象堪嫂,節(jié)省內存空間;避免頻繁的創(chuàng)建銷毀對象木柬,可以提高性能皆串;避免對共享資源的多重占用,簡化訪問眉枕;為整個系統(tǒng)提供一個全局訪問點恶复。

  • 缺點:
    不適用于變化頻繁的對象;如果實例化的對象長時間用速挑,系統(tǒng)會認為該對象是垃圾而被回收谤牡,這可能會導致對象狀態(tài)的丟失;

C++11實現線程安全的單例模式

  1. 餓漢模式
      使用餓漢模式實現單例是十分簡單的姥宝,并且有效避免了線程安全問題翅萤,因為將該單例對象定義為static變量,程序啟動即將其構造完成了腊满。代碼實現:
class Singleton {
public:
  static Singleton *GetInstance() { return singleton_; }

  static void DestreyInstance() {
    if (singleton_ != NULL) {
      delete singleton_;
    }
  }

private:
  // 防止外部構造套么。
  Singleton() = default;

  // 防止拷貝和賦值。
  Singleton &operator=(const Singleton &) = delete;
  Singleton(const Singleton &singleton2) = delete;

private:
  static Singleton *singleton_;
};

Singleton *Singleton::singleton_ = new Singleton;

int main() {
  Singleton *s1 = Singleton::GetInstance();
  std::cout << s1 << std::endl;

  Singleton *s2 = Singleton::GetInstance();
  std::cout << s2 << std::endl;

  Singleton::DestreyInstance();

  return 0;
}

2.懶漢模式
  餓漢方式不論是否需要使用該對象都將其定義出來碳蛋,可能浪費了內存胚泌,或者減慢了程序的啟動速度。所以使用懶漢模式進行優(yōu)化肃弟,懶漢模式即延遲構造對象玷室,在第一次使用該對象的時候才進行new該對象零蓉。

而懶漢模式會存在線程安全問題,最出名的解決方案就是Double-Checked Locking Pattern (DCLP)雙重檢查鎖阵苇。使用兩次判斷來解決線程安全問題并且提高效率壁公。代碼實現:

#include <iostream>
#include <mutex>

class Singleton {
public:
  static Singleton* GetInstance() {
    if (instance_ == nullptr) {
      std::lock_guard<std::mutex> lock(mutex_);
      if (instance_ == nullptr) {
        instance_ = new Singleton;
      }
    }

    return instance_;
  }
  ~Singleton() = default;
  // 釋放資源。
  void Destroy() {
    if (instance_ != nullptr) {
      delete instance_;
      instance_ = nullptr;
    }
  }

  void PrintAddress() const {
    std::cout << this << std::endl;
  }

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

private:
  static Singleton* instance_;
  static std::mutex mutex_;
};

Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;

int main() {
  Singleton* s1 = Singleton::GetInstance();
  s1->PrintAddress();

  Singleton* s2 = Singleton::GetInstance();
  s2->PrintAddress();

  return 0;
}
  1. 懶漢模式優(yōu)化
      上述代碼有一個問題绅项,當程序使用完該單例紊册,需要手動去調用Destroy()來釋放該單例管理的資源。如果不去手動釋放管理的資源(例如加載的文件句柄等)快耿,雖然程序結束會釋放這個單例對象的內存囊陡,但是并沒有調用其析構函數去關閉這些管理的資源句柄等。解決辦法就是將該管理的對象用智能指針管理掀亥。代碼如下:
#include <iostream>
#include <memory>
#include <mutex>

class Singleton {
public:
  static Singleton& GetInstance() {
    if (!instance_) {
      std::lock_guard<std::mutex> lock(mutex_);
      if (!instance_) {
        instance_.reset(new Singleton);
      }
    }

    return *instance_;
  }

  ~Singleton() = default;

  void PrintAddress() const {
    std::cout << this << std::endl;
  }

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

private:
  static std::unique_ptr<Singleton> instance_;
  static std::mutex mutex_;
};

std::unique_ptr<Singleton> Singleton::instance_;
std::mutex Singleton::mutex_;

int main() {
  Singleton& s1 = Singleton::GetInstance();
  s1.PrintAddress();

  Singleton& s2 = Singleton::GetInstance();
  s2.PrintAddress();

  return 0;
}
  1. Double-Checked Locking Pattern存在的問題
      Double-Checked Locking Pattern (DCLP)實際上也是存在嚴重的線程安全問題撞反。Scott Meyers and 和Alexandrescu寫的一篇文章里面專門分析了這種解決方案的問題C++ and the Perils of Double-Checked Locking。文章截圖:


    圖片.png

    圖片.png

    圖片.png

比如剛剛實現方式很容易發(fā)現其存在線程安全問題搪花。

if (instance_ == nullptr) { \\ 語句1
  std::lock_guard<std::mutex> lock(mutex_);
  if (instance_ == nullptr) {
    instance_ = new Singleton; \\ 語句2
  }
}

線程安全問題產生的原因是多個線程同時讀或寫同一個變量時遏片,會產生問題。
如上代碼撮竿,對于語句2是一個寫操作吮便,我們用mutex來保護instance_這個變量。但是語句1是一個讀操作幢踏,if (instance_ == nullptr)髓需,這個語句是用來讀取instance_這個變量,而這個讀操作是沒有鎖的。所以在多線程情況下,這種寫法明顯存在線程安全問題悯仙。
《C++ and the Perils of Double-Checked Locking》這篇文章中提到:

instance_ = new Singleton;

這條語句實際上做了三件事,第一件事申請一塊內存锋勺,第二件事調用構造函數,第三件是將該內存地址賦給instance_。

但是不同的編譯器表現是不一樣的∷缮辏可能先將該內存地址賦給instance_,然后再調用構造函數续扔。這是線程A恰好申請完成內存,并且將內存地址賦給instance_焕数,但是還沒調用構造函數的時候纱昧。線程B執(zhí)行到語句1,判斷instance_此時不為空堡赔,則返回該變量识脆,然后調用該對象的函數,但是該對象還沒有進行構造。

  1. 使用std::call_once實現單例
    在C++11中提供一種方法灼捂,使得函數可以線程安全的只調用一次离例。即使用std::call_once和std::once_flag。std::call_once是一種lazy load的很簡單易用的機制悉稠。實現代碼如下:
#include <iostream>
#include <memory>
#include <mutex>

class Singleton {
public:
  static Singleton& GetInstance() {
    static std::once_flag s_flag;
    std::call_once(s_flag, [&]() {
      instance_.reset(new Singleton);
    });

    return *instance_;
  }

  ~Singleton() = default;

  void PrintAddress() const {
    std::cout << this << std::endl;
  }

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;

private:
  static std::unique_ptr<Singleton> instance_;
};

std::unique_ptr<Singleton> Singleton::instance_;

int main() {
  Singleton& s1 = Singleton::GetInstance();
  s1.PrintAddress();

  Singleton& s2 = Singleton::GetInstance();
  s2.PrintAddress();

  return 0;
}

6.使用局部靜態(tài)變量實現懶漢
使用C++局部靜態(tài)變量也可解決上述問題宫蛆。

#include <iostream>

class Singleton {
public:
  static Singleton& GetInstance() {
    static Singleton intance;
    return intance;
  }

  ~Singleton() = default;

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;
};

int main() {
  Singleton& s1 = Singleton::GetInstance();
  std::cout << &s1 << std::endl;

  Singleton& s2 = Singleton::GetInstance();
  std::cout << &s2 << std::endl;

  return 0;
}

局部靜態(tài)變量可以延遲對象的構造,等到第一次調用時才進行構造的猛。
C++11中靜態(tài)變量的初始化時線程安全的耀盗。通過調試,在進行局部靜態(tài)變量初始化的時候卦尊,確實會執(zhí)行以下代碼來保證線程安全叛拷。

#include <iostream>

class Singleton {
public:
  static Singleton& GetInstance() {
    static Singleton intance;
    return intance;
  }

  ~Singleton() = default;

private:
  Singleton() = default;

  Singleton(const Singleton&) = delete;
  Singleton& operator=(const Singleton&) = delete;
};

int main() {
  Singleton& s1 = Singleton::GetInstance();
  std::cout << &s1 << std::endl;
  Singleton& s2 = Singleton::GetInstance();
  std::cout << &s2 << std::endl;
  return 0;
}

參考1
參考2
參考3

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市岂却,隨后出現的幾起案子忿薇,更是在濱河造成了極大的恐慌,老刑警劉巖躏哩,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件署浩,死亡現場離奇詭異,居然都是意外死亡震庭,警方通過查閱死者的電腦和手機瑰抵,發(fā)現死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來器联,“玉大人二汛,你說我怎么就攤上這事〔ν兀” “怎么了肴颊?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長渣磷。 經常有香客問我婿着,道長醋界,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任丘侠,我火速辦了婚禮,結果婚禮上逐样,老公的妹妹穿的比我還像新娘蜗字。我一直安慰自己打肝,他們只是感情好挪捕,可當我...
    茶點故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著级零,像睡著了一般断医。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上妄讯,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天,我揣著相機與錄音躬窜,去河邊找鬼炕置。 笑死,一個胖子當著我的面吹牛朴摊,可吹牛的內容都是我干的。 我是一名探鬼主播甚纲,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼介杆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了春哨?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤椰拒,失蹤者是張志新(化名)和其女友劉穎凰荚,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體缆毁,經...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨居荒郊野嶺守林人離奇死亡胳徽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了缚陷。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片往核。...
    茶點故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖虎锚,靈堂內的尸體忽然破棺而出衩婚,到底是詐尸還是另有隱情,我是刑警寧澤非春,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布奇昙,位于F島的核電站,受9級特大地震影響储耐,放射性物質發(fā)生泄漏。R本人自食惡果不足惜长赞,卻給世界環(huán)境...
    茶點故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一禽炬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧柳恐,春花似錦热幔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽戈锻。三九已至,卻和暖如春格遭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背骚秦。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工璧微, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人胞得。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓屹电,卻偏偏與公主長得像,于是被迫代替她去往敵國和親个扰。 傳聞我的和親對象是個殘疾皇子葱色,可洞房花燭夜當晚...
    茶點故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內容