天天看點

4建立型模式之單例模式__多線程下的懶漢式單例和餓漢式單例

//1"懶漢"模式雖然有優點,但是每次調用getinstance()靜态方法時,必須判斷

                   //      null == m_instance,使程式相對開銷增大。

         //2多線程中會導緻多個執行個體的産生,進而導緻運作代碼不正确以及記憶體的洩露。

         //3提供釋放資源的函數

讨論:   這是因為c++中構造函數并不是線程安全的。

        c++中的構造函數簡單來說分兩步:

        第一步:記憶體配置設定

        第二步:初始化成員變量

        由于多線程的關系,可能當我們在配置設定記憶體好了以後,還沒來得急初始化成員變量,就進行線程切換,另外一個線程拿到所有權後,由于記憶體已經配置設定了,但是變量初始化還        沒進行,是以列印成員變量的相關值會發生不一緻現象。

多線程下的懶漢式問題抛出:

4建立型模式之單例模式__多線程下的懶漢式單例和餓漢式單例

  

多線程下懶漢式單例的double-checked locking優化

建立mfc對話框應用程式,友善使用臨界區類對象,同步線程。

         static singleton *instantialize()

         {

                   if(pinstance == null)  //double check

                   {  

                            cs.lock(); //隻有當pinstance等于null時,才開始使用加鎖機制 二次檢查

                            if(pinstance == null)

                            {

                                     pinstance = new singleton();

                            }

                            cs.unlock();

                   }

                   return pinstance;

         }

         static singleton *pinstance;

};

singleton* singleton::pinstance = 0;

void cmy01單例優化dlg::onbnclickedbutton1()

{

         ccriticalsection  cs;

         cs.lock();

         cs.unlock();

         // todo: 在此添加控件通知處理程式代碼

}

void threadfunc(void *myipadd)

         int id = getcurrentthreadid();

         trace("\n threadfunc%d \n", id);

         singleton::instantialize()->printv();

         //singelton::getsingelton()->pirnts();

void cmy01單例優化dlg::onbnclickedbutton2()

         int i = 0;

         dword dwthreadid[201], dwthrdparam = 1;

         handle hthread[201];

         int threadnum = 3;

         for (i=0; i<threadnum; i++)

                   //hthread[i] = (handle)_beginthreadex( null, 0, &threadfunc, null, 0,&dwthreadid[i] );

                   hthread[i] = (handle)_beginthread(&threadfunc, 0 , 0 );

                   if (hthread[i] == null)

                   {

                            trace("begin thread %d error!!!\n", i);

                            break;

                   }                

                   waitforsingleobject( hthread[i], infinite );

         trace("等待線程結束\n");

                   //closehandle( hthread[i] );

         //singelton::releasesingelton();

         trace("ddddd\n");

注意:為什麼需要2次檢查 “if(pinstance == null)”?

//請思考;懶漢式的double-check是一個經典問題!為什麼需要2次檢查 “if(pinstance == null)”

場景:假設有線程1、線程2、線程3,同時資源競争。

//1)第1個、2個、3個線程執行第一個檢查,都有可能進入黃色區域(臨界區)

//2)若第1個線程進入到臨界區,第2個、第3個線程需要等待

//3)第1個線程執行完畢,cs.unlock()後,第2個、第3個線程要競争執行臨界區代碼。

//4)假若第2個線程進入臨界區,此時第2個線程需要再次判斷 if(pinstance == null)”,若第一個線程已經建立執行個體;第2個線程就不需要再次建立了。保證了單例;

//5)同樣道理,若第2個線程,cs.unlock()後,第3個線程會競争執行臨界區代碼;此時第3個線程需要再次判斷 if(pinstance == null)。通過檢查發現執行個體已經new出來,就不需要再次建立;保證了單例。

  程式的并發執行往往帶來與時間有關的錯誤,甚至引發災難性的後果。這需要引入同步機制。使用多程序與多線程時,有時需要協同兩種或多種動作,此過程就稱同步(synchronization)。引入同步機制的第一個原因是為了控制線程之間的資源同步通路,因為多個線程在共享資源時如果發生通路沖突通常會帶來不正确的後果。例如,一個線程正在更新一個結構,同時另一個線程正試圖讀取同一個結構。結果,我們将無法得知所讀取的資料是新的還是舊的,或者是二者的混合。第二個原因是有時要求確定線程之間的動作以指定的次序發生,如一個線程需要等待由另外一個線程所引起的事件。

  為了在多線程程式中解決同步問題,windows提供了四種主要的同步對象,每種對象相對于線程有兩種狀态——信号狀态(signal state)和非信号狀态(nonsignalstate)。當相關聯的同步對象處于信号狀态時,線程可以執行(通路共享資源),反之必須等待。這四種同步對象是:

(1)事件對象(event)。事件對象作為标志線上程間傳遞信号。一個或多個線程可等待一個事件對象,當指定的事件發生時,事件對象通知等待線程可以開始執行。它有兩種類型:自動重置(auto-reset)事件和手動重置(manual-reset)事件。

(2)臨界區(critical section)。臨界區對象通過提供一個程序内所有線程必須共享的對象來控制線程。隻有擁有那個對象的線程可以通路保護資源。在另一個線程可以通路該資源之前,前一個線程必須釋放臨界區對象,以便新的線程可以索取對象的通路權。

(3)互斥量(mutex semaphore)。互斥量的工作方式非常類似于臨界區,隻是互斥量不僅保護一個程序内為多個線程使用的共享資源,而且還可以保護系統中兩個或多個程序之間的的共享資源。

(4)信号量(semaphore)。信号量可以允許一個或有限個線程通路共享資源。它是通過計數器來實作的,初始化時賦予計數器以可用資源數,當将信号量提供給一個線程時,計數器的值減1,當一個線程釋放它時,計數器值加1。當計數器值小于等于0時,相應線程必須等待。信号量是windows98同步系統的核心。從本質上講,互斥量是信号量的一種特殊形式。windows/nt還提供了另外一種windows95沒有的同步對象:可等待定時器(waitable timer)。它可以封鎖線程的執行,直到到達某一具體時間。這可以用于背景任務。