• <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>

            loop_in_codes

            低調(diào)做技術(shù)__歡迎移步我的獨(dú)立博客 codemaro.com 微博 kevinlynx

            建立異步操作組件:隊(duì)列和線程

            6.25.2008

            Kevin Lynx

            引言

            在一個(gè)高效的系統(tǒng)中,我們經(jīng)常會(huì)將一些費(fèi)時(shí)的操作轉(zhuǎn)換為異步操作。例如往數(shù)據(jù)庫(kù)中寫(xiě)日志。如果數(shù)據(jù)庫(kù)
            配置在網(wǎng)絡(luò)上,那么往數(shù)據(jù)庫(kù)中插入一些日志信息將非常慢(相對(duì)于程序其他部分)。

            如何轉(zhuǎn)換為異步?

            將類(lèi)似于以上過(guò)程轉(zhuǎn)換為異步操作,一個(gè)典型的做法是:建立一個(gè)單獨(dú)的數(shù)據(jù)庫(kù)日志線程,一個(gè)線程安全的
            隊(duì)列。要寫(xiě)日志時(shí),只需要往隊(duì)列里放入數(shù)據(jù),數(shù)據(jù)庫(kù)日志線程則從這個(gè)隊(duì)列里取數(shù)據(jù)然后完成寫(xiě)操作。

            大致的過(guò)程類(lèi)似于:

            typedef safe_list<std::string> SafeList;
            SafeList gLogList; 

            // database thread.read log string.
            unsigned int __stdcall DBThread( void *p )
            {
                
            whiletrue )
                
            {
                    
            // read gLogList and write log string into the database
                }
             

                _endthreadex( 
            0 );
                
            return 0;
            }
             

            // other threads. write log string.
            void wrtie_log( const std::string &log )
            {
                gLogList.push_back( log );
            }
             


            將他們包裝起來(lái)

            我們很有可能會(huì)在同一個(gè)系統(tǒng)中多次遇到類(lèi)似需要轉(zhuǎn)換為異步操作的地方,如果每一次都手動(dòng)去創(chuàng)建一個(gè)隊(duì)列和一
            個(gè)線程,那將會(huì)多么乏味啊!懶惰的程序員喜歡重用各種代碼。所以,我自己覺(jué)得很有必要將這一切封裝起來(lái)。我
            們只需要封裝這個(gè)隊(duì)列和創(chuàng)建線程的繁瑣細(xì)節(jié),讓?xiě)?yīng)用層全部專注于具體的邏輯處理:

            template <typename _NodeType>
            class async_operator
            {
            public:
                
            /// the list node type.
                typedef _NodeType node_type;
                
            /// the list.
                typedef multi_list<node_type, Mutex, Semaphore> list_type;
                
            /// operator signature
                typedef functor<void, TYPE_LIST1( list_type& )> operator_type;
                
            /// init function signature, called when the thread starts.
                typedef functor<void> init_type;
                
            /// called before the thread exits.
                typedef functor<void> release_type; 

            public:
                
            ///
                
            /// start the thread and execute the operation.
                
            /// @param op the callback function operate the list node.
                
            /// 

                void execute( operator_type &op, init_type &init = init_type(), release_type &release = release_type() ); 

                
            ///
                
            /// exit the thread.It will block until the thread exited.
                
            ///

                void exit(); 

                
            ///
                
            /// get the list so that you can pust list nodes.
                
            ///

                list_type &list(); 

            private:
                list_type _list;
                operator_type _operator;
                init_type _init;
                release_type _release;
                thread _thread;
            }



            我利用了已有的組件:線程安全的容器multi_list、包裝任意執(zhí)行體的functor、線程維護(hù)類(lèi)thread。那么,現(xiàn)在,
            應(yīng)用層只需要定義隊(duì)列節(jié)點(diǎn)類(lèi)型,寫(xiě)應(yīng)用相關(guān)的回調(diào)函數(shù)(任意可被functor包裝的廣義函數(shù))。(見(jiàn)附件例子)

            之所以為這個(gè)組件加上init和release,是因?yàn)橛行〇|西(例如COM)需要在線程啟動(dòng)時(shí)初始化,而在線程快結(jié)束時(shí)釋放,例如對(duì)于
            使用COM的應(yīng)用來(lái)說(shuō),就需要在線程初始化時(shí)CoInitialize,結(jié)束時(shí)CoUninitialize。

            閑說(shuō)下其他東西

            在本文的附件代碼里,你可以獲取到functor、thread、multi_list這些東西,所以我有必要提一下。

            關(guān)于functor,你可以參看<實(shí)現(xiàn)functor - 增強(qiáng)型的函數(shù)指針>,基本上可以看成增強(qiáng)版的C回調(diào)函數(shù);至于multi_list,基本上
            是一個(gè)container adapter (套用下STL的概念),使用條件變量參與線程同步,據(jù)說(shuō)效率要比簡(jiǎn)單的互斥高點(diǎn);至于thread,我需要
            特別說(shuō)下:

            thread最為重要的就是為其附加了一個(gè)windows的消息隊(duì)列(只要調(diào)用PeekMessage之類(lèi)的函數(shù)該隊(duì)列就存在),本意是可以讓其他線
            程傳送數(shù)據(jù)到該線程,但是目前只用于線程退出,即其他線程可以在任何時(shí)候要求該線程安全地退出(該線程沒(méi)有阻塞的情況下,
            阻塞時(shí)獲取不到消息)。我不知道這個(gè)安全退出策略是否真的有必要存在,但是我討厭看到各種撇腳的退出方法(例如設(shè)置全局標(biāo)志
            變量,增加額外的--沒(méi)封裝前---event對(duì)象之類(lèi))。

            結(jié)束

            不知道其他人是如何做這種異步轉(zhuǎn)換操作的,在這里我只是起個(gè)拋磚引玉的作用,歡迎大家提出意見(jiàn)。

             

            例子下載

            posted on 2008-06-25 15:47 Kevin Lynx 閱讀(5051) 評(píng)論(29)  編輯 收藏 引用 所屬分類(lèi): 通用編程

            評(píng)論

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-25 16:13 true

            既然允許阻塞時(shí)不能退出,為什么不能設(shè)全局變量呢?他更及時(shí),不用參與排隊(duì),當(dāng)前日志寫(xiě)完后,就可以退出了,當(dāng)然你要考慮日志是否要保存。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程[未登錄](méi) 2008-06-25 16:22 Kevin Lynx

            @true
            貌似你誤解我意思了;)

            ’阻塞時(shí)不能退出‘:
            例如線程里 WaitFor...INFINITE了,那么它就無(wú)法處理消息,也無(wú)法判斷退出標(biāo)志變量,代碼卡在這里了。

            ’他更及時(shí),不用參與排隊(duì)‘:
            這個(gè)跟上面不是說(shuō)的同一個(gè)問(wèn)題,隊(duì)列是用于實(shí)際業(yè)務(wù)處理,例如寫(xiě)日志,上面那個(gè)說(shuō)的是線程。

              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-25 16:39 true

            一般這么搞,日志線程退出的及時(shí):WaitFor。。。設(shè)個(gè)timeout,返回后,一是檢查是否超時(shí),二檢查全局變量。除非在代碼結(jié)束階段,否則還是少用infinite。這種用法,用個(gè)模式的術(shù)語(yǔ)就是Active Object(感覺(jué)這個(gè)概念提出的很強(qiáng),思路清晰)  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-25 18:49 飯中淹

            撇腳的退出方法,其實(shí)沒(méi)什么,主要是個(gè)人喜惡。
            我喜歡用標(biāo)記,可以在任何平臺(tái)下使用都沒(méi)問(wèn)題。


            另外,我不明白為什么用了condition還要用GUARD
            那樣不是死鎖了么?
            BLOCK等待condition的時(shí)候,container被GUARD鎖死在一個(gè)線程內(nèi),其他線程調(diào)用PUSH的時(shí)候都會(huì)被擋在GUARD那里。


              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-25 21:42 Kevin Lynx

            @飯中淹
            不會(huì)的,condition和guard使用的都是同一個(gè)mutex。guard用于一般的同步,condition用于隊(duì)列元素為0時(shí)的臨界條件。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-25 23:08 影視劇

            “如果數(shù)據(jù)庫(kù)配置在網(wǎng)絡(luò)上,那么往數(shù)據(jù)庫(kù)中插入一些日志信息將非常慢”,沒(méi)錯(cuò),有時(shí)還會(huì)發(fā)生網(wǎng)絡(luò)錯(cuò)。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-26 01:52 飯中淹

            @Kevin Lynx
            是啊,當(dāng)元素為0,又正好BLOCK=TRUE的時(shí)候,就會(huì)在POP里面死循環(huán)了。
            因?yàn)镻OP里的GUARD鎖住了MUTEX,PUSH的GUARD就會(huì)等在GUARD的MUTEX那里。這樣的話,POP就永遠(yuǎn)等不到CONDITION的SIGNAL。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程[未登錄](méi) 2008-06-26 09:08 raof01

            正準(zhǔn)備寫(xiě)一篇類(lèi)似的東西。被你搶先了……
            還有沒(méi)有必要寫(xiě)呢?  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程[未登錄](méi) 2008-06-26 09:14 Kevin Lynx

            @飯中淹
            前面我說(shuō)了,condition和guard使用的是同一個(gè)mutex,在condition的wait里,會(huì)先_external_mutex.release();,然后push的時(shí)候,就不會(huì)阻塞在guard那里,于是condition.signal(元素為0時(shí),看下代碼),然后pop里的condition.wait就過(guò)了。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-26 10:30 空明流轉(zhuǎn)

            最近準(zhǔn)備把我的軟件渲染器搞成異步的,思路也差不多和LZ一樣。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-26 12:39 飯中淹

            @Kevin Lynx
            這樣會(huì)造成很多誤解啊。。。。
            WAIT完成,再去LOCK一下MUTEX么?  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-06-30 19:40 Wealth

            @true
            同意這種做法  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-07-02 09:27 yt

            我倒是有個(gè)想法,可以考慮使用一個(gè)"特殊的日志消息",根據(jù)消息內(nèi)容得知是退出消息  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-07-02 19:45 ojoj

            例如線程里 WaitFor...INFINITE了,那么它就無(wú)法處理消息,也無(wú)法判斷退出標(biāo)志變量,代碼卡在這里了。

            我是這樣判斷,設(shè)置個(gè)計(jì)數(shù)器,可以是全局/局部,在線程執(zhí)行體里不斷自加,主進(jìn)程隔一段時(shí)間去比較這個(gè)值是否增大。如果沒(méi)有,則判斷線程已經(jīng)死掉,這樣就可以避免許多其他的異常而沒(méi)有捕捉到的情況。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-02 15:22 francis

            原文如下:
            void push_back( const value_type &data )
            {
            guard<mutex_type> g( _mutex );

            if( _container.size() == 0 )
            {
            _condition.signal();
            }

            _container.push_back( data );
            }
            不知道此處這樣寫(xiě)何解?
            我覺(jué)得是不是應(yīng)該寫(xiě)成這樣:
            void push_back( const value_type &data )
            {
            guard<mutex_type> g( _mutex );

            _container.push_back( data );
            _condition.signal();
            }
            ?  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-03 10:40 Kevin Lynx

            @francis
            你可以查找‘條件變量’(condition variable)的相關(guān)資料。
            signal只在隊(duì)列尺寸為0時(shí)的push操作中調(diào)用,因?yàn)樵趐op中會(huì)在隊(duì)列尺寸為0時(shí)wait.  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 10:17 francis

            @Kevin Lynx “signal只在隊(duì)列尺寸為0時(shí)的push操作中調(diào)用”沒(méi)必要這樣吧?只要push之后signal,信號(hào)量加1, pop時(shí)wait信號(hào)量減1,為0時(shí)wait就可以了  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 12:55 Kevin Lynx

            @francis
            關(guān)鍵在于,signal和wait會(huì)對(duì)外部mutex做操作。參見(jiàn)kl_condition.h相關(guān)代碼。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 15:32 francis

            @Kevin Lynx signal并沒(méi)有對(duì)mutex操作,
            void push_back( const value_type &data )
            {
            guard<mutex_type> g( _mutex );

            _container.push_back( data );
            _condition.signal();
            }
            這樣寫(xiě)是不是好些?
              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 15:33 francis

            if( _container.size() == 0 )
            {
            _condition.signal();
            }
            這么寫(xiě)沒(méi)有必要  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 16:43 Kevin Lynx

            @francis
            我們看一次常規(guī)的同步操作:
            push_front()
            {
            guard<mutex_type> g(_mutex );
            ...
            }

            pop_front()
            {
            guadr<mutex_type> g(_mutex );
            ...
            }

            那么,在消費(fèi)者線程里可能會(huì)不斷地去檢查隊(duì)列大小是否為0(size操作同樣會(huì)涉及到同步),
            這浪費(fèi)了CPU資源。而如果:
            pop_front()
            {
            guadr<mutex_type> g(_mutex );
            while( size == 0 ) _cond.wait();
            }
            當(dāng)大小為0時(shí),wait操作將阻塞此線程,從而讓出了CPU資源(wait會(huì)阻塞)。

            另一方面,如果每次push操作都進(jìn)行條件變量的signal,這個(gè)所謂的原語(yǔ)操作開(kāi)銷(xiāo)有多大?
            查看condition::signal/wait代碼,其內(nèi)部還存在一個(gè)_wait_count的同步。另外,如果
            每次push都signal的話,那么pop操作也需要進(jìn)行wait,這樣以來(lái)它其實(shí)已經(jīng)不是條件變量,
            而是信號(hào)量了。

            我們所要的效果,就是在隊(duì)列元素為0時(shí),進(jìn)行pop操作的時(shí)候讓其阻塞而已。


              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 19:53 francis

            @Kevin Lynx 注意看,想想當(dāng)有線程在wait,同時(shí)container不為空時(shí)的情況吧,明明container里有數(shù)據(jù),但是wait的線程卻取不出來(lái)。。。 這樣看來(lái)貌似是存在邏輯錯(cuò)誤的。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-04 22:28 Kevin Lynx

            @francis
            while( _container.size() == 0 ) _condition.wait();
            僅當(dāng)為空的情況下才wait的。代碼在邏輯上不存在大問(wèn)題,因?yàn)檫@個(gè)基礎(chǔ)部件已經(jīng)被用于實(shí)際項(xiàng)目(用于保存驗(yàn)證服務(wù)器端的驗(yàn)證帳號(hào))。
              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-05 01:09 francis

            @Kevin Lynx
            while( _container.size() == 0 ) _condition.wait();
            并不能保證“有線程在wait,同時(shí)container不為空”,
            是可以模擬出現(xiàn)的。這樣的話,邏輯就出問(wèn)題了。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-05 09:00 Kevin Lynx

            @francis
            詳述下這種會(huì)出現(xiàn)問(wèn)題的情況。如果可以模擬出現(xiàn)的話,麻煩基于multi_list這個(gè)類(lèi)制造這種情況。  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-05 20:54 francis

            :-) 不是太好找,應(yīng)該是沒(méi)什么問(wèn)題的。
            下面這個(gè)情況有太多假設(shè),:-)

            有2線程在等待,
            thread1.pop ->wait
            thread2.pop ->wait

            然后下面運(yùn)行
            thread3.push
            thread3.push
            thread3.push
            。。。

            thread3 push之后會(huì)激活一個(gè)線程,假設(shè)thread1被激活,假設(shè)這個(gè)時(shí)候線程沒(méi)有切換,thread3繼續(xù)push之后才切換thread1, 這個(gè)時(shí)候, container被push了兩次,thread1這時(shí)取出一個(gè)數(shù)據(jù)處理,處理完之后切換線程,thread2還是繼續(xù)等待,thread3繼續(xù)push,這個(gè)時(shí)候假設(shè)又push了2個(gè),
            thread1由于沒(méi)有wait可以不停的取數(shù)據(jù),但是thread2就慘了,不是沒(méi)有數(shù)據(jù)可處理,而是它只能wait了。
              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-06 09:07 Kevin Lynx

            @francis
            我讀了幾遍你的這次回復(fù),希望我沒(méi)有誤解你的意思:
            thread1被激活,取數(shù)據(jù),就可能導(dǎo)致container的size為0,一旦size為0,thread3繼續(xù)push的時(shí)候就可能會(huì)激活thread2。如果OS一直沒(méi)有調(diào)度到thread2,那么,container就可能經(jīng)歷過(guò)多次size為0,size不為0,也就是thread1和thread3發(fā)生多次數(shù)據(jù)交互。thread2看起來(lái)是慘了,因?yàn)閏ontainer曾經(jīng)有數(shù)據(jù),但是thread2卻沒(méi)取到?為什么呢?thread2根本沒(méi)被OS調(diào)度到,從沒(méi)有獲取到CPU控制權(quán),它又有什么理由不wait?

            歸根結(jié)底,你說(shuō)的這個(gè)問(wèn)題,只是因?yàn)閠hread2沒(méi)有被調(diào)度到而已。
              回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-06 12:55 francis

            我說(shuō)的就是會(huì)出現(xiàn)“container不空,但是有wait的線程”這種情況,這里就是thread2在wait.如果有更多的線程做push, thread3本身也在不停的push,這個(gè)時(shí)候,處理的線程只有thread1,thread2只能wait, 因?yàn)閏ontainer一直不空,永遠(yuǎn)沒(méi)法發(fā)出激活信息。當(dāng)然這樣不會(huì)出現(xiàn)什么錯(cuò)誤,只是可能會(huì)和實(shí)際想的功能不符合,本意是想兩個(gè)線程處理container里的內(nèi)容,但是實(shí)際只有thread1在處理,thread2一直wait.  回復(fù)  更多評(píng)論   

            # re: 建立異步操作組件:隊(duì)列和線程 2008-08-06 13:11 Kevin Lynx

            @francis
            thread2沒(méi)有獲得CPU權(quán)!thread2沒(méi)有被調(diào)度!
            這里的用法完全是條件變量的標(biāo)準(zhǔn)用法,如果你硬是要從理論上認(rèn)為它有問(wèn)題,那你可以去查條件變量相關(guān)資料。這里的結(jié)構(gòu)和條件變量通用用法一樣。你甚至可以找本操作系統(tǒng)書(shū),在上面找到條件變量的使用結(jié)構(gòu)。你想推翻這一切?建議你大量查閱條件變量相關(guān)資料。如果你還認(rèn)為有問(wèn)題,你應(yīng)該去對(duì)發(fā)明條件變量的某個(gè)可能已經(jīng)死掉的牛人說(shuō):你這個(gè)條件變量的結(jié)構(gòu)有問(wèn)題。  回復(fù)  更多評(píng)論   

            欧美久久天天综合香蕉伊| 国产成人精品白浆久久69| 久久九九久精品国产| 欧美久久一区二区三区| 伊色综合久久之综合久久| 久久AV高潮AV无码AV| 国内精品久久久久久久97牛牛| 久久最近最新中文字幕大全| 久久久久99精品成人片三人毛片 | 久久香综合精品久久伊人| 亚洲av伊人久久综合密臀性色| 国产精品青草久久久久婷婷| 久久精品国产99国产精品| 国产精品乱码久久久久久软件| 99久久无码一区人妻a黑| 久久久精品久久久久特色影视| 色妞色综合久久夜夜| 国产精品久久网| 久久久久99这里有精品10| 精品综合久久久久久97超人| 尹人香蕉久久99天天拍| 久久久久免费精品国产| 久久综合久久美利坚合众国 | 亚洲第一永久AV网站久久精品男人的天堂AV | 国产精品免费福利久久| 亚洲欧美另类日本久久国产真实乱对白| 久久偷看各类wc女厕嘘嘘| 久久久久亚洲AV成人网人人网站| 久久精品人人做人人爽电影蜜月| 欧美一级久久久久久久大| 狠狠狠色丁香婷婷综合久久五月 | 日产久久强奸免费的看| 久久久久久久综合日本亚洲| 香蕉久久av一区二区三区| 亚洲精品国产综合久久一线| 国内精品欧美久久精品| 久久精品国产亚洲AV香蕉| 一本色道久久综合狠狠躁| 色偷偷91久久综合噜噜噜噜| 久久99精品免费一区二区| 久久久久久综合一区中文字幕 |