• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            cexer

            cexer
            posts - 12, comments - 334, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

            轉(zhuǎn)帖請注明出處 http://www.shnenglu.com/cexer/archive/2008/07/08/55670.html

              單件(Singelton)模式可以說是眾多設(shè)計模式當(dāng)中,理解起來最容易,概念最為簡單的一個。并且在實際的設(shè)計當(dāng)中也是使用得又最為頻繁的,甚至有很多其它的模式都要借助單件才能更好地實現(xiàn)。然而就是這樣被強烈需求的“一句話模式”(一句話就能闡述明白),雖然有無數(shù)的牛人浸淫其中,至今也沒有誰鼓搗出一個完美的實現(xiàn)。我小菜鳥一只自然更不敢逢人便談單件。不過這個貼的主題是跟單件模式是密不可分的。

              什么又叫做“線程相關(guān)的單件模式”呢?也許你已經(jīng)顧名思義猜出了八九分。不過還是允許我簡單地用實例說明一下。

              假設(shè)你要設(shè)計了一個簡單的 GUI 框架,這個框架當(dāng)中需要這樣一個全局變量(單件模式),它保存了所有窗口句柄與窗口指針的映射(我見過的數(shù)個的開源 GUI 框架都有類似的東西。)。在 WIN32 平臺上就是這樣一個簡單的東西:

                //窗口的包裝類
            class Window
            {
            HWND m_hwnd;
            public:
            bool create();
            bool destroy();

            //其它細節(jié)
            };

            //窗口句柄與其對象指針的映射
            typedef map<HWND,Window*> WindowMap;
            typedef WindowMap::iterator WindowIter;
            WindowMap theWindowMap;




              每創(chuàng)建一個窗口,就需要往這個 theWindowMap 當(dāng)中添加映射。每銷毀一個窗口,則需要從其中刪除掉相關(guān)映射。實現(xiàn)代碼類似:

                //創(chuàng)建窗口
            bool Window::create()
            {
            m_hwnd=::CreateWindow(/*參數(shù)略*/);
            if(!::IsWindow(m_hwnd))
            return false;

            theWindowMap[m_hwnd]=this; //添加映射
            return true;
            }

            //銷毀窗口
            bool Window::destroy()
            {
            ::DestroyWindow(m_hwnd);

            theWindowMap.erase(m_hwnd); //刪除映射
            return true;
            }


              你可以用任何可能的單件模式來實現(xiàn)這樣一個全局變量 theWindowMap,它會 工作得很好。但是當(dāng)如果考慮要給程序添加多線程支持(“多線程”是如此麻煩,它總愛和“但是”一起出現(xiàn),給本來進行得很順利的事情引起波折。),就會發(fā)現(xiàn)此時也許純粹的單件模式并不是最好的選擇。例如一個線程同時創(chuàng)建窗口,那么兩個線程同時調(diào)用:

                theWindowMap[m_hwnd]=this;


              這顯然不是一個原子操作,可以肯定如果你堅持這樣干你的程序會慢慢走向崩潰,幸運一點只是程序運行結(jié)果錯誤,如果你恰好那幾天印堂發(fā)暗面色發(fā)灰,說不定就因為這小小的錯誤,被無良的BOSS作為借口開除掉了,那可是個悲慘的結(jié)局。

              當(dāng)然大多數(shù)的單件模式已經(jīng)考慮到了多線程的問題。它們的解決方案就是給加上線程鎖 ,我在數(shù)個開源的 GUI 框架看到他們都采用了這種解決方案。不過這樣做,在線程同步過程當(dāng)中,會產(chǎn)生與 GUI 框架邏輯不相關(guān)的同步消耗,雖然不是什么大不了的消耗,但是客戶可能因此就選擇了你的竟?fàn)帉κ郑绻€程竟?fàn)幖ち遥趶娏铱是筚Y源的環(huán)境(如小型移動設(shè)置)當(dāng)中,這種消耗更是不可忽視的。

              實際上在應(yīng)用當(dāng)中,極少有線程需要插入刪除其它線程創(chuàng)建的窗口映射(如果確實有這種需要,那么可以肯定項目的設(shè)計上出了問題)。在這種情況下本線程創(chuàng)建窗口映射都將只是本線程存取,類似“Thread-Specific”的概念。也就是說,theWindowMap 當(dāng)中其它線程創(chuàng)建的窗口的映射對于本線程來說都是不需關(guān)心的,我們卻要為那部分不必要東西整天提心吊膽并付出運行時消耗的代價,這也有點像“穿著棉襖洗澡”。但是怎么樣才能做到更輕松爽快些呢?

              就本例問題而言,我們需要這樣一種變量來保存窗口映射,它針對每個線程有不同的值(Thread-Specific Data),這些值互不影響,并且所有線程對它的訪問如同是在訪問一個進程內(nèi)的全局變量(Singelton)。

              如果你是熟悉多線程編程的人,那么“Thread-Specific ”一定讓你想起了什么。是的,“Thread-Specific Storage ” (線程相關(guān)存存諸,簡稱 TSS ),正是我們需要的,這是大多數(shù)操作系統(tǒng)都提供了的一種線程公共資源安全機制,這種機制允許以一定方式創(chuàng)建一個變量,這個變量在所在進程當(dāng)中的每個線程當(dāng)中,可以擁有不同的值。在 WIN32 上,這個變量就稱為“索引”,其相關(guān)的值則稱為“槽”, “Thread-Local Storage”(線程局部存諸,簡稱 TLS )機制。它的提了供這樣幾個函數(shù)來定義,設(shè)置,讀取線程相關(guān)數(shù)據(jù)(關(guān)于 TLS 的更多信息,可以查閱 MSDN ):

                //申請一個“槽”的索引。
            DWORD TlsAlloc( void );

            //獲得調(diào)用線程當(dāng)中指定“槽”的值。
            VOID* TlsGetValue( DWORD dwTlsIndex );

            //設(shè)置調(diào)用線程當(dāng)中指定“槽”的值。
            BOOL TlsSetValue( DWORD dwTlsIndex,VOID* lpTlsValue );

            //釋放掉申請的“槽”的索引
            BOOL TlsFree( DWORD dwTlsIndex );

              具體使用流程方法:先調(diào)用 TlsAlloc 申請一個“索引”,然后線程在適當(dāng)時機創(chuàng)建一個對象并調(diào)用 TlsSetValue 將“索引”對應(yīng)的“槽”設(shè)置為該對象的指針,在此之后即可用 TlsGetValue 訪問該“糟”。最后在不需要的時候調(diào)用 TlsFree ,如在本例當(dāng)中,調(diào)用 TlsFree 的最佳時機是在進程結(jié)束時。

              先封裝一下 TlsAlloc 和 TlsFree  以方便對 ”索引“的管理。

                class TlsIndex
            {
            public:
            TlsIndex()
            :m_index(::TlsAlloc())
            {}

            ~TlsIndex()
            {
            ::TlsFree(m_index);
            }

            public:
            operator DWORD() const
            {
            return m_index;
            }

            private:
            DWORD m_index;
            };

              
              如你所見,類 TlsIndex 將在構(gòu)造的時候申請一個“索引”,在析構(gòu)的時候釋放此“索引”。

              在本例當(dāng)中 TlsIndex 的對象應(yīng)該存在進程的生命周內(nèi),以保證在進程退出之前,這個“索引”都不會被釋放,這樣的 TlsIndex 對象聽起來正像一個全局靜態(tài)對象,不過 Meyers Singelton (用函數(shù)內(nèi)的靜態(tài)對象實現(xiàn))在這里會更適合,因為我們不需要對這個對象的生命周末進行精確控制,只需要它在需要的時候創(chuàng)建,然后在進程結(jié)束前銷毀即可。這種方式只需要很少的代碼即可實現(xiàn),比如:

                DWORD windowMapTlsIndex()
            {
            static TlsIndex s_ti;  //提供自動管理生命周期的“索引”
            return s_ti;
            }


              利用這個“索引”,我們就能實現(xiàn)上述“Thread-Specific”的功能:

                WindowMap* windowMap()
            {
            WindowMap* wp=reinterpret_cast<WindowMap*>(::TlsGetValue(windowMapTlsIndex()));
            if(!wp)
            {
            wp=new WindowMap();
            ::TlsSetValue(windowMapTlsIndex(),wp);
            }
            return wp;
            }

            #define theWindowMap *(windowMap())

              
              注意各線程訪問以上的代碼不會存在竟?fàn)帯_@樣就實現(xiàn)了一個線程安全且無線程同步消耗版本的“全局對象” theWindowMap 。我們甚至不用改變Window::create,Window::destory,queryWindow 的代碼,

              這幾個簡單的函數(shù)看起來似乎不像一個“模式”,但是它確實是的。

              現(xiàn)在總結(jié)一下“線程相關(guān)的單件模式”的概念:保證一個類在一個線程當(dāng)中只有一個實例,并提供一個訪問它的線程內(nèi)的訪問點的模式。

              為了不重復(fù)地制造車輪,我將此類應(yīng)用的模式封裝了一下:

                template<typename TDerived>
            class TlsSingelton
            {
            typedef TDerived _Derived;
            typedef TlsSingelton<TDerived> _Base;

            public:
            static _Derived* tlsInstance()
            {
            return tlsCreate();
            }

            protected:
            static _Derived* tlsCreate()
            {
            _Derived* derived=tlsGet();
            if(derived)
            return derived;

            derived=new _Derived();
            if(derived && TRUE==::TlsSetValue(tlsIndex(),derived))
            return derived;

            if(derived)
            delete derived;

            return NULL;
            }

            static bool tlsDestroy()
            {
            _Derived* derived=tlsGet();
            if(!derived)
            return false;

            delete derived;
            return true;
            }

            static DWORD tlsIndex()
            {
            static TlsIndex s_tlsIndex;
            return s_tlsIndex;
            }

            private:
            static _Derived* tlsGet()
            {
            return reinterpret_cast<_Derived*>(::TlsGetValue(tlsIndex()));
            }

            static bool tlsSet(_Derived* derived)
            {
            return TRUE==::TlsSetValue(tlsIndex(),derived);
            }

            //noncopyable
            private:
            TlsSingelton(const _Base&);
            TlsSingelton& operator=(const _Base&);
            };


              將 tlsCreate,tlsDestroy 兩個函數(shù)設(shè)置為保護成員,是為了防止一些不三不四吊爾啷噹的程序隨意地刪除。

              示例:

                class WindowMapImpl:public TlsSingelton<WindowMap>
            {
            WindowMap m_map;
            public:
            WidnowMap& theWindowMapImpl()
            {
            return m_map;
            }

            public:
            ~WindowMapImpl();

            protected:
            WindowMapImpl(); //只能通過tlsCreate創(chuàng)建
            friend class _Base;
            };

            #define theWindowMap (WindowMapImpl::tlsInstance()->theWindowMapImpl())



              仍不需要修改原有窗口代碼。

            Feedback

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-09 16:57 by www.helpsoff.com.cn
            哈哈,有意思!看到一半的時候,心想有必要引入tls嗎,只要設(shè)好編譯選項,map應(yīng)該是線程安全的呀;不過看到后面,把tls引入然后封裝到原有實現(xiàn)上去,覺得很精彩,其實已經(jīng)跳出了討論所謂singleton和線程安全的范疇了,雖然目的是這個。

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-09 18:32 by cexer
            呵呵謝謝耐心看完。map可以通過編譯器設(shè)置線程安全?我還不知道呢。不過引入tls與“線程相關(guān)的單件”,是為了可以讓更多更復(fù)雜的此類應(yīng)用更容易實現(xiàn)。還有map的線程安全是由實現(xiàn)提供的(非標(biāo)準),應(yīng)該不是所有編譯器都支持的吧?

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-10 01:44 by www.helpsoff.com.cn
            我大致記得用cl編譯鏈接生成binary的時候帶"/MT"就是連接多線程庫,不過印象不深了,博主有興趣可以看看。

            我同意博主的說法,引入tls確實是為實現(xiàn)類似應(yīng)用做好了封裝。

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-10 09:02 by cexer
            @www.helpsoff.com.cn
            這個"/MT"應(yīng)該指的鏈接是微軟運行時庫,C運行時庫。C++標(biāo)準庫對包括map在內(nèi)的容器在多線程環(huán)境當(dāng)中的情況沒有采取任何的保護措施。

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-10 14:09 by www.helpsoff.com.cn
            對頭,/MT是指連接微軟的runtime lib,相對于標(biāo)準庫來將,這個庫的實現(xiàn)是支持多線程的。不過博主說的對,連接這個庫并不能保證map是線程安全了。獻丑了...

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-10 22:53 by 夢在天涯
            TlsObject<***> 這個東東哪里來的那,TLS倒是蠻好用的哦!

            http://www.shnenglu.com/mzty/archive/2007/08/01/28892.html

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2008-07-10 23:22 by cexer
            @夢在天涯
            應(yīng)該是TlsSingelton,我寫錯了哈,謝謝提醒。

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2009-08-31 22:42 by stidio
            這樣寫,用處不大,其實線程局部化本身用處就不大
            如果這樣寫,同一個線程中持有的是不同的映射實例,而很多情況(不僅僅是窗口映射),這樣做的目的,是為了根據(jù)一個index獲得一個結(jié)果,也就是查詢;
            如果這樣最,對于跨線程查詢,你必須破壞你的設(shè)計;

            不知道是不是我理解的問題,單件模式的引出,是為了確定資源的唯一性;而你的這個恰恰不是;例如:
            張三,李四的老板是王五,那王五對于張三,李四來說,是他的"單件"
            張7,張8的爸爸是張9,那張9是單件

            而你卻構(gòu)建了一個,張9和王五的集合,說這是另外4個人的單件,這并不符合唯一性條件;

            關(guān)于單件模型的多線程問題,其實單件本身沒多線程問題,多線程問題的引入是在對單件對象的使用上;如果說單件存在著多線程問題,那也僅僅需要在創(chuàng)建時鎖定(比如說2個線程同時獲得,都為空,創(chuàng)建2次;這樣需要在創(chuàng)建時鎖定,并做二次判斷,如
            if(xx == 0) {
            lock();
            if(xx == 0)
            xx = new XX;
            ....
            }
            而其實大多數(shù)情況不需要這樣來折騰;


            超哥不錯哈,出去后的確進步了很多;

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2009-09-01 12:15 by cexer
            @stidio
            這個設(shè)計是設(shè)計給不跨線程的應(yīng)用的,主要考慮在這種應(yīng)用下,如果線程太多,都去查詢同一個全局的東西,大多數(shù)線程都一直處于等待資源的狀態(tài),比較浪費CPU時間。
            這是在公司時寫的哈,出來后倒沒寫了,感覺是人越來越懶了,寫程序越來越?jīng)]激情

            # re: 線程相關(guān)的單件模式(Thread-Specific Singelton)  回復(fù)  更多評論   

            2010-05-12 16:10 by ZeroQ
            #define theWindowMap (WindowMapImpl::tlsInstance()->theWindowMapImpl())
            上面一行中,WindowMapImpl::tlsInstance()返回的是WindowMap實例,而WindowMap并沒有theWindowMapImpl()方法,不知道這樣是如何實現(xiàn)的。請教嘍。。。
            亚洲人AV永久一区二区三区久久| 97久久超碰国产精品旧版| 亚洲午夜久久久精品影院| 久久久久一区二区三区| 久久综合久久综合久久综合| 91亚洲国产成人久久精品网址| 国产精品激情综合久久| 免费精品久久久久久中文字幕| 亚洲精品乱码久久久久久不卡| 久久99热这里只频精品6| 亚洲国产另类久久久精品黑人 | 亚洲精品午夜国产VA久久成人| 久久久久久国产精品无码下载| 日产精品久久久久久久性色 | 狠狠狠色丁香婷婷综合久久五月 | 中文字幕日本人妻久久久免费| 无码国产69精品久久久久网站| 91久久成人免费| 久久综合久久美利坚合众国| AV狠狠色丁香婷婷综合久久 | 久久露脸国产精品| 无码日韩人妻精品久久蜜桃| 久久成人精品| 国产高潮国产高潮久久久| 日韩亚洲国产综合久久久| 国产成人精品白浆久久69| 伊人久久一区二区三区无码| 青青草原综合久久大伊人精品| 99久久精品国产一区二区 | 国产高潮国产高潮久久久91| 久久久久久伊人高潮影院| 99久久夜色精品国产网站| 欧洲精品久久久av无码电影| 香蕉久久夜色精品国产2020| 91久久精品电影| 伊人热人久久中文字幕| 久久精品99久久香蕉国产色戒| 麻豆精品久久久久久久99蜜桃| 久久国产精品二国产精品| 2020久久精品国产免费| 久久久久亚洲精品无码蜜桃|