• <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>
            posts - 200, comments - 8, trackbacks - 0, articles - 0

            五種I/O 模式:
            【1】        阻塞 I/O           (Linux下的I/O操作默認是阻塞I/O,即open和socket創(chuàng)建的I/O都是阻塞I/O)
            【2】        非阻塞 I/O        (可以通過fcntl或者open時使用O_NONBLOCK參數(shù),將fd設(shè)置為非阻塞的I/O)
            【3】        I/O 多路復(fù)用     (I/O多路復(fù)用,通常需要非阻塞I/O配合使用)
            【4】        信號驅(qū)動 I/O    (SIGIO)
            【5】        異步 I/O

             

            一般來說,程序進行輸入操作有兩步:
            1.等待有數(shù)據(jù)可以讀
            2.將數(shù)據(jù)從系統(tǒng)內(nèi)核中拷貝到程序的數(shù)據(jù)區(qū)。

            對于sock編程來說:

                     第一步:   一般來說是等待數(shù)據(jù)從網(wǎng)絡(luò)上傳到本地。當數(shù)據(jù)包到達的時候,數(shù)據(jù)將會從網(wǎng)絡(luò)層拷貝到內(nèi)核的緩存中;

                     第二步:   是從內(nèi)核中把數(shù)據(jù)拷貝到程序的數(shù)據(jù)區(qū)中。

             

            阻塞I/O模式                            //進程處于阻塞模式時,讓出CPU,進入休眠狀態(tài)
                    阻塞 I/O 模式是最普遍使用的 I/O 模式。是Linux系統(tǒng)下缺省的IO模式。

                   大部分程序使用的都是阻塞模式的 I/O 。

                   一個套接字建立后所處于的模式就是阻塞 I/O 模式。(因為Linux系統(tǒng)默認的IO模式是阻塞模式)


            對于一個 UDP 套接字來說,數(shù)據(jù)就緒的標志比較簡單:
            (1)已經(jīng)收到了一整個數(shù)據(jù)報
            (2)沒有收到。
            而 TCP 這個概念就比較復(fù)雜,需要附加一些其他的變量。

                   一個進程調(diào)用 recvfrom  ,然后系統(tǒng)調(diào)用并不返回知道有數(shù)據(jù)報到達本地系統(tǒng),然后系統(tǒng)將數(shù)據(jù)拷貝到進程的緩存中。 (如果系統(tǒng)調(diào)用收到一個中斷信號,則它的調(diào)用會被中斷)

               我們稱這個進程在調(diào)用recvfrom一直到從recvfrom返回這段時間是阻塞的。當recvfrom正常返回時,我們的進程繼續(xù)它的操作。

             
             ---------------------------------------------------------------------------------

            非阻塞模式I/O                           //非阻塞模式的使用并不普遍,因為非阻塞模式會浪費大量的CPU資源。
                   當我們將一個套接字設(shè)置為非阻塞模式,我們相當于告訴了系統(tǒng)內(nèi)核: “當我請求的I/O 操作不能夠馬上完成,你想讓我的進程進行休眠等待的時候,不要這么做,請馬上返回一個錯誤給我。”
                  我們開始對 recvfrom 的三次調(diào)用,因為系統(tǒng)還沒有接收到網(wǎng)絡(luò)數(shù)據(jù),所以內(nèi)核馬上返回一個 EWOULDBLOCK的錯誤。

                  第四次我們調(diào)用 recvfrom 函數(shù),一個數(shù)據(jù)報已經(jīng)到達了,內(nèi)核將它拷貝到我們的應(yīng)用程序的緩沖區(qū)中,然后 recvfrom 正常返回,我們就可以對接收到的數(shù)據(jù)進行處理了。
                  當一個應(yīng)用程序使用了非阻塞模式的套接字,它需要使用一個循環(huán)來不聽的測試是否一個文件描述符有數(shù)據(jù)可讀(稱做 polling(輪詢))。應(yīng)用程序不停的 polling 內(nèi)核來檢查是否 I/O操作已經(jīng)就緒。這將是一個極浪費 CPU資源的操作。這種模式使用中不是很普遍。

             
            例如:
                     對管道的操作,最好使用非阻塞方式!
             
             
              ---------------------------------------------------------------------------------
             

            I/O多路復(fù)用                             //針對批量IP操作時,使用I/O多路復(fù)用,非常有好。

                   在使用 I/O 多路技術(shù)的時候,我們調(diào)用 select()函數(shù)和 poll()函數(shù)或epoll函數(shù)(2.6內(nèi)核開始支持),在調(diào)用它們的時候阻塞,而不是我們來調(diào)用 recvfrom(或recv)的時候阻塞。
                   當我們調(diào)用 select函數(shù)阻塞的時候,select 函數(shù)等待數(shù)據(jù)報套接字進入讀就緒狀態(tài)。當select函數(shù)返回的時候, 也就是套接字可以讀取數(shù)據(jù)的時候。 這時候我們就可以調(diào)用 recvfrom函數(shù)來將數(shù)據(jù)拷貝到我們的程序緩沖區(qū)中。
                    對于單個I/O操作,和阻塞模式相比較,select()和poll()或epoll并沒有什么高級的地方。
                    而且,在阻塞模式下只需要調(diào)用一個函數(shù):
                                         讀取或發(fā)送函數(shù)。
                               在使用了多路復(fù)用技術(shù)后,我們需要調(diào)用兩個函數(shù)了:
                                         先調(diào)用 select()函數(shù)或poll()函數(shù),然后才能進行真正的讀寫。

                   多路復(fù)用的高級之處在于::

                          它能同時等待多個文件描述符,而這些文件描述符(套接字描述符)其中的任意一個進入讀就緒狀態(tài),select()函數(shù)就可以返回。

             
            IO 多路技術(shù)一般在下面這些情況中被使用:
            1、當一個客戶端需要同時處理多個文件描述符的輸入輸出操作的時候(一般來說是標準的輸入輸出和網(wǎng)絡(luò)套接字),I/O 多路復(fù)用技術(shù)將會有機會得到使用。
            2、當程序需要同時進行多個套接字的操作的時候。
            3、如果一個 TCP 服務(wù)器程序同時處理正在偵聽網(wǎng)絡(luò)連接的套接字和已經(jīng)連接好的套接字。
            4、如果一個服務(wù)器程序同時使用 TCP 和 UDP 協(xié)議。
            5、如果一個服務(wù)器同時使用多種服務(wù)并且每種服務(wù)可能使用不同的協(xié)議(比如 inetd就是這樣的)。
             
             
             
            異步IO模式有::
                   1、信號驅(qū)動I/O模式
                   2、異步I/O模式
            信號驅(qū)動I/O模式                                                   //自己沒有用過。

                   我們可以使用信號,讓內(nèi)核在文件描述符就緒的時候使用 SIGIO 信號來通知我們。我們將這種模式稱為信號驅(qū)動 I/O 模式。


            為了在一個套接字上使用信號驅(qū)動 I/O 操作,下面這三步是所必須的。

            (1)一個和 SIGIO信號的處理函數(shù)必須設(shè)定。
            (2)套接字的擁有者必須被設(shè)定。一般來說是使用 fcntl 函數(shù)的 F_SETOWN 參數(shù)來
            進行設(shè)定擁有者。
            (3)套接字必須被允許使用異步 I/O。一般是通過調(diào)用 fcntl 函數(shù)的 F_SETFL 命令,O_ASYNC為參數(shù)來實現(xiàn)。

                   雖然設(shè)定套接字為異步 I/O 非常簡單,但是使用起來困難的部分是怎樣在程序中斷定產(chǎn)生 SIGIO信號發(fā)送給套接字屬主的時候,程序處在什么狀態(tài)。

            1.UDP 套接字的 SIGIO 信號                    (比較簡單)
            在 UDP 協(xié)議上使用異步 I/O 非常簡單.這個信號將會在這個時候產(chǎn)生:
            1、套接字收到了一個數(shù)據(jù)報的數(shù)據(jù)包。
            2、套接字發(fā)生了異步錯誤。
                    當我們在使用 UDP 套接字異步 I/O 的時候,我們使用 recvfrom()函數(shù)來讀取數(shù)據(jù)報數(shù)據(jù)或是異步 I/O 錯誤信息。
            2.TCP 套接字的 SIGIO 信號                   (不會使用)
                      不幸的是,異步 I/O 幾乎對 TCP 套接字而言沒有什么作用。因為對于一個 TCP 套接字來說,SIGIO 信號發(fā)生的幾率太高了,所以 SIGIO 信號并不能告訴我們究竟發(fā)生了什么事情。
            在 TCP 連接中, SIGIO 信號將會在這個時候產(chǎn)生:
            l  在一個監(jiān)聽某個端口的套接字上成功的建立了一個新連接。
            l  一個斷線的請求被成功的初始化。
            l  一個斷線的請求成功的結(jié)束。
            l  套接字的某一個通道(發(fā)送通道或是接收通道)被關(guān)閉。
            l  套接字接收到新數(shù)據(jù)。
            l  套接字將數(shù)據(jù)發(fā)送出去。

            l  發(fā)生了一個異步 I/O 的錯誤。

            一個對信號驅(qū)動 I/O 比較實用的方面是 NTP(網(wǎng)絡(luò)時間協(xié)議 Network Time Protocol)服務(wù)器,它使用 UDP。這個服務(wù)器的主循環(huán)用來接收從客戶端發(fā)送過來的數(shù)據(jù)報數(shù)據(jù)包,然后再發(fā)送請求。對于這個服務(wù)器來說,記錄下收到每一個數(shù)據(jù)包的具體時間是很重要的。

            因為那將是返回給客戶端的值,客戶端要使用這個數(shù)據(jù)來計算數(shù)據(jù)報在網(wǎng)絡(luò)上來回所花費的時間。圖 6-8 表示了怎樣建立這樣的一個 UDP 服務(wù)器。

             

             

            異步I/O模式             //比如寫操作,只需用寫,不一定寫入磁盤(這就是異步I/O)的好處。異步IO的好處效率高。
                  當我們運行在異步 I/O 模式下時,我們?nèi)绻脒M行 I/O 操作,只需要告訴內(nèi)核我們要進行 I/O 操作,然后內(nèi)核會馬上返回。具體的 I/O 和數(shù)據(jù)的拷貝全部由內(nèi)核來完成,我們的程序可以繼續(xù)向下執(zhí)行。當內(nèi)核完成所有的 I/O 操作和數(shù)據(jù)拷貝后,內(nèi)核將通知我們的程序。
            異步 I/O 和  信號驅(qū)動I/O的區(qū)別是:
                    1、信號驅(qū)動 I/O 模式下,內(nèi)核在操作可以被操作的時候通知給我們的應(yīng)用程序發(fā)送SIGIO 消息。

                    2、異步 I/O 模式下,內(nèi)核在所有的操作都已經(jīng)被內(nèi)核操作結(jié)束之后才會通知我們的應(yīng)用程序。

            轉(zhuǎn)自:http://blog.163.com/xychenbaihu@yeah/blog/static/13222965520112163171778/

            posted @ 2012-12-16 12:21 鑫龍 閱讀(332) | 評論 (0)編輯 收藏

            孤兒進程: 即一個其父進程已經(jīng)終止的進程。 孤兒進程由 init 進程“收養(yǎng)”,init 進程ID為1,因此被收養(yǎng)的孤兒進程的父進程便更新為1。

            孤兒進程組: 一個進程組中的所有進程的父進程要么是該進程組的一個進程,要么不是該進程組所在的會話中的進程。 一個進程組不是孤兒進程組的條件是,該組中有一個進程其父進程在屬于同一個會話的另一個組中。

            GNU解釋了為什么會提出孤兒進程組的概念:
                   When a controlling process terminates, its terminal becomes free and a new session can be established on it. (In fact, another user could log in on the terminal.) This could cause a problem if any processes from the old session are still trying to use that terminal.
            To prevent problems, process groups that continue running even after the session leader has terminated are marked as orphaned process groups.
                   When a process group becomes an orphan, its processes are sent a SIGHUP signal. Ordinarily, this causes the processes to terminate. However, if a program ignores this signal or establishes a handler for it, it can continue running as in the orphan process group even after its controlling process terminates; but it still cannot access the terminal any more.

            當一個終端控制進程(即會話首進程)終止后,那么這個終端可以用來建立一個新的會話。這可能會產(chǎn)生一個問題,原來舊的會話(一個或者多個進程組的集合)中的任一進程可再次訪問這個的終端。為了防止這類問題的產(chǎn)生,于是就有了孤兒進程組的概念。當一個進程組成為孤兒進程組時,posix.1要求向孤兒進程組中處于停止狀態(tài)的進程發(fā)送SIGHUP(掛起)信號,系統(tǒng)對于這種信號的默認處理是終止進程,然而如果無視這個信號或者另行處理的話那么這個掛起進程仍可以繼續(xù)執(zhí)行。


            以下摘自網(wǎng)絡(luò):

                   終端的問題涉及幾個概念,那就是進程組,會話,作業(yè),下面會分別進行介紹。會話包含了一系列的進程,這些進程按照不同的執(zhí)行內(nèi)容會組織成若干進程組,一個會話內(nèi)的所有進程都必須是該會話首長進程的后代,這樣就保證了這些進程都是由該會話首長進程直接或者間接開啟的,只有這樣的才能保證這些進程確實是在會話首長進程耳目視線之內(nèi)的,同時,孤兒進程組不再受到會話首長進程的控制。
            作業(yè):
            只有一個終端,但是有很多事情要同時做,或者起碼分時做,不能先做完一件事再做另一件,怎么辦?畢竟啟動一個進程后該進程就會獨占終端啊,畢竟shell會將它設(shè)置為前臺進程組進程啊。這就是作業(yè)的功能,只需要在一個命令后加一個&符號就可以了,比如我要執(zhí)行x,那么就敲入:
            x &
            shell的結(jié)果是:
            [1] 1234
            此處1234是進程的pid,而1則是作業(yè)的id,這樣這個x就不占用終端了,shell可以啟動其它的進程或者作業(yè)了,比如又啟動了作業(yè)2:
            [2] 4321
                   此時想起作業(yè)1需要使用一下終端來輸入一些信息了,那么就使用:fg %1將作業(yè)1置于前臺(作業(yè)1中目前只有一個進程),置于前臺的作業(yè)如何重新放到后臺呢?只需要用SIGSTOP信號停止前臺使用終端的進程即可,然后該進程就放開終端的占用了
            進程組:一個作業(yè)就是一個進程組,單獨的進程可以獨占一個進程組也可以加入同一會話的別的進程組,必須要滿足的條件是,同一進程組的所有進程都要是一個會話的后代。所謂的進程組是為了組織作業(yè)或者組織同一類任務(wù)的。
            控制終端:
                   一個會話的建立者有權(quán)力申請一個控制終端,在該控制終端中可以接受標準輸入,可以發(fā)送shell理解的控制快捷鍵,可以創(chuàng)建作業(yè)并且使用會話頭進程提供的作業(yè)控制功能。控制終端只能由會話頭進程創(chuàng)建,并且控制終端是獨占的,只要有一個進程將一個終端當成了控制終端,別的進程不管它是誰都不能這么做了,tty_open中的最后有下面幾行代碼
            if (!noctty &&
                current->signal->leader &&
                !current->signal->tty &&
                tty->session == 0) {
                task_lock(current);
                current->signal->tty = tty;
                task_unlock(current);
                current->signal->tty_old_pgrp = 0;
                tty->session = current->signal->session;  //設(shè)置session
                tty->pgrp = process_group(current);
            }
            可見別的進程是無權(quán)申請控制終端的。
                 這個控制終端平時給誰用呢?最不容被懷疑的會話首長申請了終端,因為如果連他都值得懷疑的話,后面的屬于他的孩子進程們都值得懷疑了,首長申請的終端就是給這些孩子們用的,首長將這些孩子們分成了若干的進程組,指定一個組為前臺進程組,只有這個前臺進程組的進程才能使用控制終端。bash一般會作為會話首長存在,bash將為一個執(zhí)行的命令都創(chuàng)建一個進程組,它在接受一個命令準備執(zhí)行的時候會將該進程設(shè)置為前臺進程組,它在接受了命令行后加&的命令時,會創(chuàng)建一個作業(yè),并且將它設(shè)定為后臺進程組,那么此時前臺是誰呢,是bash自己哦。后臺進程不能使用終端,如果使用&執(zhí)行了內(nèi)部帶有諸如getchar之類函數(shù)的進程,那么其將會收到SIGTTIN信號,不過可以使用fg命令將這類進程提到前臺。
            控制進程:
                   很顯然,首先控制進程是一個會話的首長進程,另外即使是會話首長也只能通過終端來控制別的進程,所謂的控制就是發(fā)送信號而不是操作內(nèi)存之類的,這也是進程間通信的一種方式。因此所謂的控制進程就是申請到控制終端的進程。
            孤兒進程組:
                   有孤兒進程,對應(yīng)的也有孤兒進程組的概念。為何引入這個概念以及這個概念的引入需要OS的實現(xiàn)者作些什么呢?先看兩個前提,首先,posix用一個session的概念來描述一次用戶的登錄以及該用戶在此次登錄后的操作,然后用作業(yè)的概念描述不同操作的內(nèi)容,最后才用進程的概念描述不同操作中某一個具體的工作;其次,unix最初將所有的進程組織成了樹的形式,這樣就便于追蹤每個進程也便于管理(想想看,人類政治社會也是一個類似樹形結(jié)構(gòu):君主專制,兩院制等)。有了上述兩個前提事情就很明白了,一切都是為了便于管理,一切都是為了登錄用戶的安全,即此次登錄用戶的作業(yè)是不能被下個登錄用戶所控制的,即使它們的用戶名一致也是不行的,因此所謂的孤兒進程組簡單點說就是脫離了創(chuàng)造它的session控制的,離開其session眼線的進程組,unix中怎樣控制進程,怎樣證明是否在自己的眼線內(nèi),那就是樹形結(jié)構(gòu)了,只要處于以自己為根的子樹的進程就是自己眼線內(nèi)的進程,這個進程就是受到保護的,有權(quán)操作的,而在別的樹枝上的進程原則上是觸動不得的(又想說說windows的遠程線程創(chuàng)建了,可是說的話還要接著說其復(fù)雜的令牌機制,否則windows粉絲不服氣,所以不說了罷),unix中建立進程使用fork,自然地這么一“叉子”就形成了自己的一個樹枝,當然在自己眼線了,一般對于登錄用戶而言一個會話起始于一次login之后的shell,只要該用戶不logout,在該終端的shell上執(zhí)行的所有的非守護進程都是該shell的后代進程,因此它們組成一個會話,全部在shell的眼線中,一個會話終止于會話首長的death。現(xiàn)在考慮一下終端上的shell退出后的情景,按照規(guī)定,該終端上所有的進程都過繼給了別的進程,大多數(shù)情況是init進程,然后緊接著另外一個用戶登錄了這個終端或者知道前一個登錄用戶密鑰的另一個有不好念頭的人登錄了該終端,當然為其啟動的shell創(chuàng)建了一個新的session,由于之前登錄的用戶已退出,現(xiàn)在登錄的用戶由于之前用戶的進程組都成了孤兒進程組,所以它再有惡意也不能控制它們了,那些孤兒進程組中的成員要么繼續(xù)安全的運行,要么被shell退出時發(fā)出的SIGHUP信號殺死。
                   POSIX的規(guī)定是鐵的紀律,而unix或者linux的不管是內(nèi)核還是shell的實現(xiàn)則是一種遵守紀律的方式,鐵的紀律要求作業(yè)控制要以session為基本,就是說不能操作別的session內(nèi)的進程組,所以類似fg和bg等命令就不能操作孤兒進程,因此如果由于后臺進程組由于讀寫終端被SIGSTOP信號停了,而后它又成了孤兒進程組的成員,那怎么辦?別的session的作業(yè)控制命令又不能操作它,即使ps -xj找到了它然后手工發(fā)送了SIGCONT,那么它還是沒法使用終端,這是POSIX的另一個紀律要求的,只有唯一和終端關(guān)聯(lián)的session中的前臺進程組的進程可以使用終端,因此只要有一個shell退出了,最好的辦法就是將其session內(nèi)的所有的進程都干掉,因此SIGHUP的原意就是如此,但是完全可以忽略這個信號或者自己定義對該信號的反應(yīng)。POSIX的基本限制就是session的ID是不能設(shè)置的,因為它是受保護的基本單位,但是進程組的ID是可以設(shè)置的,畢竟它只是區(qū)分了不能的作業(yè),最后進程的PID也是不能設(shè)置的,因為它是進程的內(nèi)秉屬性,形成樹形進程結(jié)構(gòu)的關(guān)鍵屬性。
                 POSIX對孤兒進程組的定義:組中沒有一個進程的父進程和自己屬于同一個會話但是不同進程組的。

            轉(zhuǎn)自 http://xingyunbaijunwei.blog.163.com/blog/static/765380672011112633634628/

            posted @ 2012-12-16 10:42 鑫龍 閱讀(384) | 評論 (0)編輯 收藏

            1 什么時候需要同步?

            2 在linux中, 線程同步機制有哪些?各適用在怎樣的條件?進程同步有哪些?對應(yīng)的數(shù)據(jù)類型、API 各有哪些?
            在當前的POSIX標準中有三種線程同步機制,它們分別是:互斥量、讀寫鎖、條件變量。
            關(guān)于進程同步機制,POSIX定義了一種信號燈,而system V 定義了另外一種信號燈。
            ---------------------------------------------------------------------- 
            線程同步

            (1)、互斥量(mutex)

              互斥量本質(zhì)上是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成后釋放互斥量上的鎖。

              對互斥量進行加鎖以后,任何其它試圖再次對互斥量加鎖的線程將會被阻塞直到當前線程釋放該互斥鎖。如果釋放互斥鎖時有多個線程阻塞,所有在該互斥鎖上的阻塞線程都會變成可運行狀態(tài),第一個變?yōu)檫\行狀態(tài)的線程可以對互斥量加鎖,其它線程將會看到互斥鎖依然被鎖住,只能回去再次等待它重新變?yōu)榭捎谩T谶@種情況下,每次只有一個線程可以向前執(zhí)行。

            pthread_mutex_t 
            pthread_mutex_init();
            pthread_mutex_lock();
            pthread_mutex_unlock();
            pthread_mutex_destroy();
            (2)、讀寫鎖

              讀寫鎖與互斥量類似,不過讀寫鎖允許更高的并行性。互斥量要么是鎖住狀態(tài)要么是不加鎖狀態(tài),而且一次只有一個線程可以對其加鎖。

              讀寫鎖可以由三種狀態(tài):讀模式下加鎖狀態(tài)寫模式下加鎖狀態(tài)不加鎖狀態(tài)。一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。

              在讀寫鎖是寫加鎖狀態(tài)時,在這個鎖被解鎖之前,所有試圖對這個鎖加鎖的線程都會被阻塞。當讀寫鎖在讀加鎖狀態(tài)時,所有試圖以讀模式對它進行加鎖的線程都可以得到訪問權(quán),但是如果線程希望以寫模式對此鎖進行加鎖,它必須阻塞直到所有的線程釋放讀鎖。雖然讀寫鎖的實現(xiàn)各不相同,但當讀寫鎖處于讀模式鎖住狀態(tài)時,如果有另外的線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨后的讀模式鎖請求。這樣可以避免讀模式鎖長期占用,而等待的寫模式鎖請求一直得不到滿足。

              讀寫鎖非常適合于對數(shù)據(jù)結(jié)構(gòu)讀的次數(shù)遠大于寫的情況。當讀寫鎖在寫模式下時,它所保護的數(shù)據(jù)結(jié)構(gòu)就可以被安全地修改,因為當前只有一個線程可以在寫模式下?lián)碛羞@個鎖。當讀寫鎖在讀狀態(tài)下時,只要線程獲取了讀模式下的讀寫鎖,該鎖所保護的數(shù)據(jù)結(jié)構(gòu)可以被多個獲得讀模式鎖的線程讀取。

              讀寫鎖也叫做共享-獨占鎖,當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當他以寫模式鎖住時,它是以獨占模式鎖住的。

            pthread_rwlock_t
            pthread_rwlock_init();
            pthread_rwlock_rdlock();
            pthread_rwlock_wrlock();
            pthread_rwlock_unlock();
            pthread_rwlock_destroy();
            (3) 、條件變量(condition)和監(jiān)視器(monitor)

              條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發(fā)生。

              條件本身是由互斥量保護的。線程在改變條件狀態(tài)前必須首先鎖住互斥量,其它線程在獲得互斥量之前不會察覺到這種改變,因此必須鎖定互斥量以后才能計算條件。

            pthread_cond_t
            pthread_cond_init();
            pthreadf_cond_wait();
            pthread_cond_signal();
            pthread_cond_destroy();
            ----------------------------------------------------------------------
            進程同步 
            信號量(Semaphore),有時被稱為信號燈,是在多線程環(huán)境下使用的一種設(shè)施,是可以用來保證兩個或多個關(guān)鍵代碼段不被并發(fā)調(diào)用。在進入一個關(guān)鍵代碼段之前,線程必須獲取一個信號量;一旦該關(guān)鍵代碼段完成了,那么該線程必須釋放信號量。其它想進入該關(guān)鍵代碼段的線程必須等待直到第一個線程釋放信號量。為了完成這個過程,需要創(chuàng)建一個信號量VI,然后將Acquire Semaphore VI以及Release Semaphore VI分別放置在每個關(guān)鍵代碼段的首末端。確認這些信號量VI引用的是初始創(chuàng)建的信號量。

            POSIX信號燈:
            sem_t
            sem_open();
            sem_init();
            sem_wait();
            sem_post();
            sem_close();
            sem_unlink();
            SysV信號燈:
            semget();
            semop();
            semctl();
            3 線程同步機制和進程同步機制有什么區(qū)別?哪個使用的范圍更大?
            顯然進程同步機制可以用于線程同步,而線程同步機制不能用于進程同步。




            posted @ 2012-12-14 11:46 鑫龍 閱讀(498) | 評論 (0)編輯 收藏

            線程的分離狀態(tài)決定一個線程以什么樣的方式來終止自己。
            線程的默認屬性,一般是非分離狀態(tài),
            這種情況下,原有的線程等待創(chuàng)建的線程結(jié)束。
            只有當pthread_join()函數(shù)返回時,創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。
            而分離線程沒有被其他的線程所等待,自己運行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。
            程序員應(yīng)該根據(jù)自己的需要,選擇適當?shù)姆蛛x狀態(tài)。

             

             

            關(guān)于分離線程的一種用法(轉(zhuǎn))

             

            講到分離線程,先得從僵尸進程講起(抱歉,確實不知道線程是否有僵尸一說)。

            關(guān)于僵尸進程:一般情況下進程終止的時候,和它相關(guān)的系統(tǒng)資源也并不是主動釋放的,而是進入一種通常稱為“僵尸”(zombie)的狀態(tài)。它所占有 的資源一直被系統(tǒng)保留,直到它的父進程(如果它直接的父進程先于它去世,那么它將被init進程所收養(yǎng),這個時候init就是它的父進程)顯式地調(diào)用 wait系列函數(shù)為其“收尸”。為了讓父進程盡快知道它去世的消息,它會在它死去的時候通過向父進程發(fā)送SIGCHLD信號的方式向其“報喪”。

            所以一旦父進程長期運行,而又沒有顯示wait或者waitpid,同時也沒處理SIGCHLD信號,這個時候init進程,就沒辦法來替子進程來收尸。這個時候,子進程就真的成了”僵尸“了。

            同理:

            如果一個線程調(diào)用了這個函數(shù),那么當這個線程終止的時候,和它相關(guān)的系統(tǒng)資源將被自動釋放,系統(tǒng)不用也不能用pthread_join()等待其退 出。有的時候分離線程更好些,因為它潛在地減少了一個線程回收的同步點,并且pthread_join()這個API確實也是相當?shù)仉y用。

            為了讓主線程省去去子線程收尸的過程,可以使用

            int pthread_detach(pthread_t thread);

            來讓子線程處于分離狀態(tài),就不需要父線程再pthread_join了。

            我們來看一種分離線程的用法。上次別人問道一種情況,我發(fā)現(xiàn)必須要分離子線程:

            void* task1(void*);

            void usr();

            int p1;

            int main()
            {
            p1
            =0;
            usr();               
            //調(diào)用這個認為是你的觸發(fā)函數(shù)
                getchar();
            return 1;
            }

            void usr()
            {
            pthread_t  pid1;
            pthread_attr_t attr;
            /*這里做你的事情*/
            if(p1==0)
            {    pthread_attr_init(
            &attr);
            pthread_attr_setdetachstate(
            &attr, PTHREAD_CREATE_DETACHED);       //因為你的線程不便于等待的關(guān)系,設(shè)置為分離線程吧    
                        pthread_create(&pid1, &attr, task1, NULL);

            }

            }

            void* task1(void *arg1)
            {
            p1
            =1;                           //讓子線程不會被多次調(diào)用
                int i=0;
            printf(
            "thread1 begin./n");
            for(i=0;i<100;i++)
            {
            sleep(
            2);                  
            printf(
            "At thread1: i is %d/n",i);       
            usr();                    
            //繼續(xù)調(diào)用
               }
            pthread_exit();
            }
             

            我 們看到,在這里task1這個線程函數(shù)居然會多次調(diào)用其父線程里的函數(shù),顯然usr函數(shù)里,我們無法等待task1結(jié)束,反而task1會多次調(diào)用 usr,一旦我們在usr里pthread_join,則在子線程退出前,有多個usr函數(shù)會等待,很浪費資源。所以,此處,將task1設(shè)置為分離線程 是一種很好的做法。

            posted @ 2012-12-14 11:01 鑫龍 閱讀(212) | 評論 (0)編輯 收藏

            這是一個網(wǎng)友的提問:

            在 UNIX的system()函數(shù)實現(xiàn)過程中,要求在父進程中忽略掉SIGINT和SIGQUIT信號,但是要將SIGCHLD信號阻塞(在子進程中將 SIGINT和SIGQUIT信號設(shè)為默認,SIGCHLD信號解鎖)。子進程執(zhí)行完畢后,在父進程中調(diào)用waitpid(pid_t, &state, 0)。問題: 
            1、若父進程已被waitpid阻塞,在子進程返回時,此時在父進程中SIGCHLD被阻塞(BLOCK),父進程收不到SIGCHLD信號,waitpid()函數(shù)能否正確返回,收集到子進程的信息? 
            2、 waitpid若能正確完成,在以后父進程中,將SIGCHLD信號UNBLOCK,用sigprocmask()函數(shù)解鎖,書上說,在 sigprocmask()函數(shù)返回以前,會將以前阻塞的信號發(fā)送給進程,父進程是否還能收到SIGCHLD信號?若能收到何必在開始時將SIGCHLD 進程阻塞。 


            簡單的對這個問題的解釋是wait及其變體并不是通過sigchld信號來知道子進程狀態(tài)的。
            sigprocmask 阻塞的是有signal或sigaction設(shè)置的信號處理程序,即帶有SIGCHLD_Handle()等處理函數(shù)。wait不是靠接收sigchld 信號獲得子進程的退出狀態(tài)的,如果進程中同時設(shè)置了signal和wait,則子進程退出后發(fā)出sigchld信號,交到signal的信號處理程序處 理,wait接收到子進程退出狀態(tài)。
            只是接收sigchld,而不調(diào)用wait還是會使子進程僵死的。一般的只有調(diào)用wait才能使子進程不成為僵死進程(除了2次fork 等或其他一些手段)。

            概括下:waitpid不是依靠SIGCHLD是否到達來判斷子進程是否退出,但是如果設(shè)置了SIGCHLD的處理函數(shù),那么就需要等待SIGCHLD信號 的發(fā)生并完成信號處理函數(shù),waitpid才能接收到子進程的退出狀態(tài)。在APUE中的system()實現(xiàn)中阻塞了SIGCHLD信號,但是并沒有設(shè)置 信號處理函數(shù),所以waitpid在阻塞了SIGCHLD的情況下依然能正常返回,因為SIGCHLD在未設(shè)置信號處理函數(shù)的情況下不會影響到 waitpid的工作。至于為什么要阻塞SIGCHLD信號呢?那就是為了防止其他程序(main除了會調(diào)用system還會使用其他程序)設(shè)置了 SIGCHLD的信號處理函數(shù),如果其他程序設(shè)置了SIGCHLD信號處理函數(shù),在waitpid等待子程序的返回前,要去處理SIGCHLD信號處理程 序,如果阻塞了該信號,就不會去處理該信號處理程序,防止多余信息在system()中的出現(xiàn)。

            posted @ 2012-12-14 10:58 鑫龍 閱讀(383) | 評論 (0)編輯 收藏

            早期的Unix系統(tǒng),如果進程在一個‘慢’系統(tǒng)調(diào)用中阻塞時,捕獲到一個信號,這個系統(tǒng)調(diào)用被中斷,調(diào)用返回錯誤,設(shè)置errno為EINTR。系統(tǒng)調(diào)用被分為慢系統(tǒng)調(diào)用和其他兩大類別。

                慢系統(tǒng)調(diào)用可以被永久阻塞,包括以下幾個類別

                   (1)讀寫‘慢’設(shè)備(包括pipe,終端設(shè)備,網(wǎng)絡(luò)連接等)。讀時,數(shù)據(jù)不存在,需要等待;寫時,緩沖區(qū)滿或其他原因,需要等待。讀寫磁盤文件一般不會阻塞。

                   (2)當打開某些特殊文件時,需要等待某些條件,才能打開。例如:打開中斷設(shè)備時,需要等到連接設(shè)備的modem響應(yīng)才能完成。

                   (3)pause和wait函數(shù)。pause函數(shù)使調(diào)用進程睡眠,直到捕獲到一個信號。wait等待子進程終止。

                   (4)某些ioctl操作。

                   (5)某些IPC操作。

                有些情況下,即使操作被信號中斷,還是要繼續(xù)執(zhí)行該操作,即需要重啟該操作。那么,程序需要檢查系統(tǒng)調(diào)用的錯誤類型是否為EINTR,如果是,表明系統(tǒng)調(diào)用被中斷,則重新啟動操作。典型代碼如下所示:

                 again:
                      if ((n = read(fd, buf, BUFFSIZE)) < 0) {
                         if (errno == EINTR)
                              goto again;     /* just an interrupted system call */
                        /* handle other errors */
                      }

                4.2BSD為了簡化程序的操作,提供了自動重啟某些被中斷系統(tǒng)調(diào)用的功能,這些系統(tǒng)調(diào)用包括ioctl,read,readv,write,writev,wait,waitpid。前五個函數(shù)當它們操作慢設(shè)備時,才會被中斷。這可能給那些不希望自動重啟這些系統(tǒng)調(diào)用的應(yīng)用帶來麻煩,所以4.3BSD允許進程在指定信號上關(guān)閉此功能。

                POSIX.1允許實現(xiàn)重新啟動系統(tǒng)調(diào)用,但沒有強制要求。SUS給sigaction增加了一個XSI擴展標記SA_RESTART,要求被該信號中斷的系統(tǒng)調(diào)用被自動重啟。

             

            別忘了--要處理被中斷的系統(tǒng)調(diào)用

            一般慢速系統(tǒng)調(diào)用基本規(guī)則是:當阻塞于某個慢系統(tǒng)系統(tǒng)調(diào)用的一個進程捕獲某個信號且相應(yīng)信號處理函數(shù)返回時,該系統(tǒng)調(diào)用可能要返回

            ENINTR錯誤。

             

            問:linux會重啟某些被中斷的系統(tǒng)調(diào)用嗎?

             

            處理的例子:

            for( ; ;) {
                 if (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < 0) 
                {
                if (errno == EINTR)
                    continue;
                }
                else
                {
                    errsys("accept error");
                }
            }

             

            在tcp socket 中,connect()被中斷后是不能被重啟的?如何處理呢

            可以采用select來等待連接完成

             

            系統(tǒng)調(diào)用被信號中斷和自動重啟動

                當進程正在執(zhí)行一個系統(tǒng)調(diào)用時,如果被信號中斷,這時會發(fā)生什么呢?

            當一個低速調(diào)用阻塞期間捕捉到一個信號, 則該系統(tǒng)調(diào)用就被中斷不再繼續(xù)執(zhí)行。 該系統(tǒng)調(diào)用返回出錯,起errono設(shè)置為EINTR。 因為發(fā)生信號, 進程捕捉到它, 這將是一個很好的機會來喚醒阻塞的系統(tǒng)調(diào)用。

            但有一個問題就是如果該系統(tǒng)調(diào)為read(), 正在等待終端輸入, 如果被信號中斷的話, 難免會影響整個程序的正確性, 所以有些系統(tǒng)使這類系統(tǒng)調(diào)用自動重啟動。就是一旦被某信號中斷, 立即再啟動。

            如下面的signal1函數(shù)實現(xiàn): 

            #include <signal.h>
            #include "ourhdr.h"

            typedef void Sigfunc(int);

            Sigfunc *
            signal1(int signo, Sigfunc *func)
            {
            struct sigaction        act, oact;

            act.sa_handler = func;
            sigemptyset(&act.sa_mask);
            act.sa_flags = 0;

            if (signo ==SIGALRM)
            {
            #ifdef SA_INTERRUPT
            act.sa_flags |= SA_INTERRUPT;
            #endif
            }
            else
            {
            #ifdef SA_RESTART
            act.sa_flags |= SA_RESTART;   /*這里對所有的信號都設(shè)置了自動再啟動,唯獨沒有SIGALRM*/
            #endif
            }

            if (sigaction(signo, &act, &oact)<0)
            return(SIG_ERR);
            return (oact.sa_handler);
            }

            為什么偏偏面對SIGALRM信號, 系統(tǒng)調(diào)用不設(shè)置自動重啟動呢? 這時為了我們方便給read等低速系統(tǒng)調(diào)用定時。 我們不希望它一遇到某個信號變自動重啟動,也不希望它無限制的阻塞下去。 于是用alarm()進行定時, 一旦超出某個時間, 便被ALRM信號中斷喚醒,且不再重啟動。


            下面這段程序用來測試上面的signal1函數(shù), 對一個read系統(tǒng)調(diào)用, 如何給它定時的:

            #include <signal.h>
            #include "ourhdr.h"
            #include "10-12.c"

            #define MAXLINE 1024

            static void sig_alrm(int);

            int
            main(void)
            {
            int     n;
            char line[MAXLINE];

            if (signal1(SIGALRM, sig_alrm) == SIG_ERR)
            perror("signal");

            alarm(10);
            if ( (n = read(STDIN_FILENO, line, MAXLINE)) < 0)
            perror("read");
            alarm(0);
            write(STDOUT_FILENO, line, n);
            write(STDOUT_FILENO, "exit\n", 5);

            exit(0);
            }

            static void
            sig_alrm(int signo)
            {
            write(STDOUT_FILENO, "recieved signal -ALRM\n", 22);
            return;
            }

            在我的系統(tǒng)中, 如果調(diào)用默認的signal函數(shù), 該read()系統(tǒng)調(diào)用將會自動重啟動, 所謂的alarm定時也就不起作用了。

            posted @ 2012-12-10 16:51 鑫龍 閱讀(799) | 評論 (2)編輯 收藏

                 摘要:        在UNIX系統(tǒng)中,作業(yè)控制:允許在一個終端上啟動多個作業(yè)(進程組),控制哪一個作業(yè)可以存取該終端,以及哪些作業(yè)在后臺運行。一句話就是,作業(yè)控制是解決不同作業(yè)(也就是進程組)對控制終端這個資源的使用的競爭問題。作業(yè)控制作為Shell的一個特性存在,也就是說有的shell支持作業(yè)控制這個作業(yè)功能,有的不支持。linux下常用的bash是支持的作業(yè)控...  閱讀全文

            posted @ 2012-12-09 14:30 鑫龍 閱讀(254) | 評論 (0)編輯 收藏

            如果一個進程fork一個子進程,但不要它等待子進程終止,也不希望子進程處于僵死狀態(tài)直到父進程終止,實現(xiàn)這一要求的技巧是調(diào)用fork2次。

            下面是實例代碼:

            #include <stdio.h>
            #include <stdlib.h>
            #include <sys/wait.h>

            int main(void)
            {
                    pid_t pid;

                    if((pid = fork()) < 0) {
                            printf("error: fork error.\n");
                    } else if(pid == 0) {
                            if((pid = fork()) < 0)
                                    printf("error: fork error.\n");
                            else if(pid > 0)
                                    exit(0);
                            /* we are the second child; our parent becomes init as soon as
                             * our real parent calls exit() in the statement above. Here is 
                             * where we had continue executing , knowing that when we are 
                             * done, init will reap our status. 
                             
            */
                            sleep(2);
                            printf("second child, parent pid = %d\n", getppid());
                            exit(0);
                    }

                    if(waitpid(pid, NULL, 0) != pid)
                            printf("error, waitpid error.\n");

                    exit(0);
            }
                  這里核心思想是,把第二個子進程的父進程換成init進程,因為init進程會立馬終止僵死進程。而最開始的父進程也因為直接子進程(第一個進程)終止,不需要阻塞。
                  第二個子進程調(diào)用sleep以保證在打印父進程ID時第一個字進程已終止。在fork之后,父子進程都可以繼續(xù)執(zhí)行,并且我們無法預(yù)知哪個會限制性。在fork之后,如果不是第二個子進程休眠,那么它可能比其父進程先執(zhí)行,于是它打印的父進程ID將是創(chuàng)建它的父進程,而不是init進程。

            posted @ 2012-12-02 18:12 鑫龍 閱讀(375) | 評論 (0)編輯 收藏

            最近一直在看STL和Boost,源碼里邊好多涉及到模板元編程技術(shù),簡單了解一下,備忘(Boost Python中的涉及模板元的部分重點關(guān)注一下)。

            范例引入

            // 主模板
            template<int N>
            struct Fib
            {
                
            enum { Result = Fib<N-1>::Result + Fib<N-2>::Result };
            };

            // 完全特化版
            template <>
            struct Fib<1>
            {
                
            enum { Result = 1 };
            };


            // 完全特化版
            template <>
            struct Fib<0>
            {
                
            enum { Result = 0 };
            };

            int main()
            {
                
            int i = Fib<10>::Result;
                
            // std::cout << i << std::endl;
            }

            主要思想

            利用模板特化機制實現(xiàn)編譯期條件選擇結(jié)構(gòu),利用遞歸模板實現(xiàn)編譯期循環(huán)結(jié)構(gòu),模板元程序則由編譯器在編譯期解釋執(zhí)行。

            優(yōu)劣及適用情況

            通過將計算從運行期轉(zhuǎn)移至編譯期,在結(jié)果程序啟動之前做盡可能多的工作,最終獲得速度更快的程序。也就是說模板元編程的優(yōu)勢在于:

              1.以編譯耗時為代價換來卓越的運行期性能(一般用于為性能要求嚴格的數(shù)值計算換取更高的性能)。通常來說,一個有意義的程序的運行次數(shù)(或服役時間)總是遠遠超過編譯次數(shù)(或編譯時間)。

              2.提供編譯期類型計算,通常這才是模板元編程大放異彩的地方。

            模板元編程技術(shù)并非都是優(yōu)點:

              1.代碼可讀性差,以類模板的方式描述算法也許有點抽象。

              2.調(diào)試困難,元程序執(zhí)行于編譯期,沒有用于單步跟蹤元程序執(zhí)行的調(diào)試器(用于設(shè)置斷點、察看數(shù)據(jù)等)。程序員可做的只能是等待編譯過程失敗,然后人工破譯編譯器傾瀉到屏幕上的錯誤信息。

              3.編譯時間長,通常帶有模板元程序的程序生成的代碼尺寸要比普通程序的大,

              4.可移植性較差,對于模板元編程使用的高級模板特性,不同的編譯器的支持度不同。

            總結(jié):

            模板元編程技術(shù)不適用普通程序員的日常應(yīng)用,它常常會做為類庫開發(fā)的提供技術(shù)支持,為常規(guī)模板代碼的內(nèi)核的關(guān)鍵算法實現(xiàn)更好的性能或者編譯期類型計算。模板元程序幾乎總是應(yīng)該與常規(guī)代碼結(jié)合使用被封裝在一個程序庫的內(nèi)部。對于庫的用戶來說,它應(yīng)該是透明的。

            工程應(yīng)用實例

            1. Blitz++:由于模板元編程最先是因為數(shù)值計算而被發(fā)現(xiàn)的,因此早期的研究工作主要集中于數(shù)值計算方面,Blitz++庫利用模板將運行期計算轉(zhuǎn)移至編譯期的庫,主要提供了對向量、矩陣等進行處理的線性代數(shù)計算。

            2.Loki:將模板元編程在類型計算方面的威力應(yīng)用于設(shè)計模式領(lǐng)域,利用元編程(以及其他一些重要的設(shè)計技術(shù))實現(xiàn)了一些常見的設(shè)計模式之泛型版本。Loki庫中的Abstract Factory泛型模式即借助于這種機制實現(xiàn)在不損失類型安全性的前提下降低對類型的靜態(tài)依賴性。

            3.Boost:元編程庫目前主要包含MPL、Type Traits和Static Assert等庫。 Static Assert和Type Traits用作MPL的基礎(chǔ)。Boost Type Traits庫包含一系列traits類,用于萃取C++類型特征。另外還包含了一些轉(zhuǎn)換traits(例如移除一個類型的const修飾符等)。Boost Static Assert庫用于編譯期斷言,如果評估的表達式編譯時計算結(jié)果為true,則代碼可以通過編譯,否則編譯報錯。

            技術(shù)細節(jié)

            模板元編程使用靜態(tài)C++語言成分,編程風格類似于函數(shù)式編程,在模板元編程中,主要操作整型(包括布爾類型、字符類型、整數(shù)類型)常量和類型,不可以使用變量、賦值語句和迭代結(jié)構(gòu)等。被操縱的實體也稱為元數(shù)據(jù)(Metadata),所有元數(shù)據(jù)均可作為模板參數(shù)。

            由于在模板元編程中不可以使用變量,我們只能使用typedef名字和整型常量。它們分別采用一個類型和整數(shù)值進行初始化,之后不能再賦予新的類型或數(shù)值。如果需要新的類型或數(shù)值,必須引入新的typedef名字或常量。

            其它范例


            // 僅聲明
            struct Nil;

            // 主模板
            template <typename T>
            struct IsPointer
            {
                
            enum { Result = false };
                typedef Nil ValueType;
            };

            // 局部特化
            template <typename T>
            struct IsPointer<T*>
            {
                
            enum { Result = true };
                typedef T ValueType;
            };

            // 示例
            int main()
            {
                cout 
            << IsPointer<int*>::Result << endl;
                cout 
            << IsPointer<int>::Result << endl;
                IsPointer
            <int*>::ValueType i = 1;
                
            //IsPointer<int>::ValueType j = 1;  
                
            // 錯誤:使用未定義的類型Nil
            }


            //主模板
            template<bool>
            struct StaticAssert;

            // 完全特化
            template<> 
            struct StaticAssert<true>
            {};

            // 輔助宏
            #define STATIC_ASSERT(exp)\
            { StaticAssert
            <((exp) != 0)> StaticAssertFailed; }

            int main()
            {
                STATIC_ASSERT(
            0>1);
            }
             

             

            References:

            http://club.topsage.com/thread-421469-1-1.html

            http://wenku.baidu.com/view/c769720df78a6529647d539d.html

            Blitz++: http://www.oonumerics.org/blitz .
            Loki :http://sourceforge.net/projects/loki-lib

            Boost:http://www.boost.org/

            posted @ 2012-12-01 20:01 鑫龍 閱讀(350) | 評論 (0)編輯 收藏

                  什么是traits,為什么人們把它認為是C++ Generic Programming的重要技術(shù)?

                  簡潔地說,traits如此重要,是因為此項技術(shù)允許系統(tǒng)在編譯時根據(jù)類型作一些決斷,就好像在運行時根據(jù)值來做出決斷一樣。更進一步,此技術(shù)遵循“另增一個間接層”的諺語,解決了不少軟件工程問題,traits使您能根據(jù)其產(chǎn)生的背景(context) 來做出抉擇。這樣最終的代碼就變得清晰易讀,容易維護。如果你正確運用了traits技術(shù),你就能在不付出任何性能和安全代價的同時得到這些好處,或者能夠契合其他解決方案上的需求。
                  先舉個淺顯易懂的例子來說明traits的用法:
            //首先假如有以下一個泛型的迭代器類,其中類型參數(shù) T 為迭代器所指向的類型:
            template <typename T>
            class myIterator
            {

            };
                   那么當使用myIterator時,怎樣才能知道它所指向元素的類型呢?一種解決方案是為這個類加入一個內(nèi)嵌類型:
            template <typename T>
            class myIterator
            {
            typedef T value_type;

            };
                  當使用myIterator時,可以通過myIterator::value_type來獲得相應(yīng)的myIterator所指向的類型。下面舉例使用:
            template <typename T>
            typename myIterator<T>::value_type func(myIterator<T> i)
            {

            }
                  這里定義了一個函數(shù)func,返回值類型為參數(shù)i所指的類型,也就是模板參數(shù)T,那么為什么不直接使用模板參數(shù)T,而要繞著圈去使用那個value_type呢?所以我們返回來,當修改func函數(shù)時,它能夠適應(yīng)所有類型的迭代器,不是更好嗎?如下所示:
            template <typename I> //這里的I可以是任意類型的迭代器
            typename I::value_type func(I i)
            {

            }
                  現(xiàn)在,任意定義了value_type內(nèi)嵌類型的迭代器都可以做為func的參數(shù)了,并且func的返回值的類型將與相應(yīng)迭代器所指的元素的類型一致。至此一切問題似乎都已解決,并且似乎并沒有使用任何特殊的技術(shù)。
                  然而當考慮到以下情況時,新的問題便顯現(xiàn)出來了:原生指針也完全可以做為迭代器來使用,然而顯然沒有辦法為原生指針添加一個value_type的內(nèi)嵌類型,如此一來func()函數(shù)就不能適用原生指針了,這不能不說是一大缺憾。那么有什么辦法可以解決這個問題呢?此時不禁想到了用Traits萃取類型信息。可以不直接使用myIterator的value_type,而是通過Traits類來把這個信息提取出來:(不同的類型,可以有不同的提取方式)
            template <typename T>
            class Traits
            {
            typedef typename T::value_type value_type;
            };
                  這樣以后就可以通過Traits<myIterator>::value_type來提取出myIterator中的value_type,于是func函數(shù)改寫成:
            template <typename I> //這里的I可以是任意類型的迭代器
            typename Traits<I>::value_type Foo(I i)
            {

            }
                  然而,即使這樣,那個原生指針的問題仍然沒有解決,因為Trait類還是沒辦法獲得原生指針的相關(guān)信息。于是不妨將Traits偏特化(partial specialization):(通過特化、重載特化等手段產(chǎn)出不同的提取方式)
            template <typename T>
            class Traits<T*> //注意 這里針對原生指針進行了偏特化
            {
            typedef typename T value_type;
            };
                 通過上面這個Traits的偏特化版本,一個T*類型的指針所指向的元素的類型為T。如此一來,我們func函數(shù)就完全可以適用于原生指針了。比如:
            int * p;
            .
            int i = func(p);
                 Traits會自動推導(dǎo)出p所指元素的類型為int,從而func正確返回。
            -----------------------------------------------------------------------------------------------------------------------------------------------------------
                 現(xiàn)在再看一個更加一般的例子——smart pointers。(智能指針)
                 假設(shè)你正在設(shè)計一個SmartPtr模板類,對于一個smart pointer 來說,它的最大的用處是可以自動管理內(nèi)存問題,同時在其他方面又像一個常規(guī)指針。但是有些C++的Smart pointer實現(xiàn)技術(shù)卻非常令人難以理解。這一殘酷的事實帶來了一個重要實踐經(jīng)驗:你最好盡一切可能一勞永逸,寫出一個出色的、具有工業(yè)強度的 smart pointer來滿足你所有的需求。此外,你通常不能修改一個類來適應(yīng)你的smart pointer,所以你的SmartPtr一定要足夠靈活。
                 有不少類層次使用引用計數(shù)(reference counting)以及相應(yīng)的函數(shù)管理對象的生存期。然而,并沒有reference counting的標準實現(xiàn)方法,每一個C++庫的供應(yīng)商在實現(xiàn)的語法和/或語義上都有所不同。例如,在你的應(yīng)用程序中有這樣兩個interfaces:
            第一種智能指針--大部分的類實現(xiàn)了RefCounted接口:
            class RefCounted
            {
            public:
            virtual void IncRef() = 0;
            virtual bool DecRef() = 0;
            // if you DecRef() to zero references, the object is destroyed
            // automatically and DecRef() returns true
            virtual ~RefCounted() {}
            };
            第二種智能指針--第三方提供的Widget類使用不同的接口:
            class Widget
            {
            public:
            void AddReference();
            int RemoveReference(); 
            // returns the remaining number of references; it's the client's
            // responsibility to destroy the object

            };
                  不過你并不想維護兩個smart pointer類,你想讓兩種類共享一個SmartPtr。一個基于traits的解決方案把兩種不同的接口用語法和語義上統(tǒng)一的接口包裝起來,建立針對普通類的通用模板,而針對Widget建立一個特殊化版本,如下:
            template <class T>
            class RefCountingTraits
            {
            static void Refer(T* p)
            {
            p->IncRef(); // assume RefCounted interface
            }
            static void Unrefer(T* p)
            {
            p->DecRef(); //assume RefCounted interface
            }
            };
            template<>
            class RefCountingTraits<Widget>
            {
            static void Refer(Widget* p)
            {
            p->AddReference(); //use Widget interface
            }
            static void Unrefer(Widget* p)
            {
            //use Widget interface
            If (p->RemoveReference() == 0)
            delete p;
            }
            };
                  在SmartPtr里,我們像這樣使用RefCountingTraits:
            template <class T>
            class SmartPtr
            {
            private:
            typedef RefCountingTraits<T> RCTraits;
            T* pointee_;
            public:

            ~SmartPtr()
            {
            RCTraits::Unrefer(pointee_);
            }
            };
                  當然在上面的例子里,你可能會爭論說你可以直接特殊化Widget類的SmartPtr的構(gòu)造與析構(gòu)函數(shù)。你可以使用把模板特殊化技術(shù)用在 SmartPtr本身,而不是用在traits上頭,這樣還可以消除額外的類。盡管對這個問題來說這種想法沒錯,但還是由一些你需要注意的缺陷:
            1. 這么干缺乏可擴展性。如果給SmartPtr再增加一個模板參數(shù),你不能特殊化這樣一個SmartPtr<T. U>,其中模板參數(shù)T是Widget,而U可以為其他任何類型。
            2. 最終代碼不那么清晰。Trait有一個名字,而且把相關(guān)的東西很好的組織起來,因此使用traits的代碼更加容易理解。相比之下,用直接特殊化SmartPtr成員函數(shù)的代碼,看上去更招黑客的喜歡。
                  用繼承機制的解決方案,就算本身完美無瑕,也至少存在上述的缺陷。解決這樣一個變體問題,使用繼承實在是太笨重了。此外,通常用以取代繼承方案的另一種經(jīng)典機制——containment,用在這里也顯得畫蛇添足,繁瑣不堪。相反,traits方案干凈利落,簡明有效,物合其用,恰到好處。
                  Traits的一個重要的應(yīng)用是“interface glue”(接口膠合劑),通用的、可適應(yīng)性極強的適配子。如果不同的類對于一個給定的概念有著不同的實現(xiàn),traits可以把這些實現(xiàn)再組織統(tǒng)一成一個公共的接口。對于一個給定類型提供多種TRAITS:現(xiàn)在,我們假設(shè)所有的人都很喜歡你的SmartPtr模板類,直到有一天,在你的多線程應(yīng)用程序里開始現(xiàn)了神秘的bug。你發(fā)現(xiàn)罪魁禍首是Widget,它的引用計數(shù)函數(shù)并不是線程安全的。現(xiàn)在你不得不親自實現(xiàn)Widget:: AddReference和Widget::RemoveReference,最合理的位置應(yīng)該是在RefCountingTraits中,打上個補丁吧:
            // Example 7: Patching Widget's traits for thread safety
            template <>
            class RefCountingTraits<Widget>
            {
            static void Refer(Widget* p)
            {
            Sentry s(lock_); // serialize access
            p->AddReference();
            }
            static void Unrefer(Widget* p)
            {
            Sentry s(lock_); // serialize access
            if (p->RemoveReference() == 0)
            delete p;
            }
            private:
            static Lock lock_;
            };
                   不幸的是,雖然你重新編譯、測試之后正確運行,但是程序慢得像蝸牛。仔細分析之后發(fā)現(xiàn),你剛才的所作所為往程序里塞了一個糟糕的瓶頸。實際上只有少數(shù)幾個Widget是需要能夠被好幾個線程訪問的,余下的絕大多數(shù)Widget都是只被一個線程訪問的。你要做的是告訴編譯器按你的需求分別使用多線程traits和單線程traits這兩個不同版本。你的代碼主要使用單線程traits。
                   如何告訴編譯器使用哪個traits?一種方法是把traits作為另一個模板參數(shù)傳給SmartPtr。缺省情況下傳遞老式的traits模板,而用特定的類型實例化特定的模板。
            template <class T, class RCTraits = RefCountingTraits<T> >
            class SmartPtr
            {

            };
                  你對單線程版的RefCountingTraits<Widget>不做改動,而把多線程版放在一個單獨的類中:
            class MtRefCountingTraits
            {
            static void Refer(Widget* p)
            {
            Sentry s(lock_); // serialize access
            p->AddReference();
            }
            static void Unrefer(Widget* p)
            {
            Sentry s(lock_); // serialize access
            if (p->RemoveReference() == 0)
            delete p;
            }
            private:
            static Lock lock_;
            };
                   現(xiàn)在你可將SmartPtr<Widget>用于單線程目的,將SmartPtr<Widget,MtRefCountingTraits>用于多線程目的。
                   最后,以SGI STL中的__type_traits結(jié)束本篇討論,在SGI 實現(xiàn)版的STL中,為了獲取高效率,提供了__type_traits,用來提取類的信息,比如類是否擁有trival的構(gòu)造、析構(gòu)、拷貝、賦值操作,然后跟據(jù)具體的信息,就可提供最有效率的操作。以下摘錄cygwin的gcc3.3源碼,有改動,在<type_traits.h>中。
            struct __true_type {};
            struct __false_type {};
            template <class _Tp>
            struct __type_traits
            {
            typedef __true_type    this_dummy_member_must_be_first;
            typedef __false_type    has_trivial_default_constructor;
            typedef __false_type    has_trivial_copy_constructor;
            typedef __false_type    has_trivial_assignment_operator;
            typedef __false_type    has_trivial_destructor;
            typedef __false_type    is_POD_type;
            };
                    對于普通類來講,為了安全起見,都認為它們擁有non-trival的構(gòu)造、析構(gòu)、拷貝、賦值函數(shù),POD是指plain old data。接下來對C++的原生類型(bool,int, double之類)定義了顯式的特化實現(xiàn),以double為例:
            template<> 
            struct __type_traits<long double> {
            typedef __true_type    has_trivial_default_constructor;
            typedef __true_type    has_trivial_copy_constructor;
            typedef __true_type    has_trivial_assignment_operator;
            typedef __true_type    has_trivial_destructor;
            typedef __true_type    is_POD_type;
            };
            還有,對所有的原生指針來講,它們的構(gòu)造、析構(gòu)等操作也是trival的,因此有:
            template <class _Tp>
            struct __type_traits<_Tp*> {
            typedef __true_type    has_trivial_default_constructor;
            typedef __true_type    has_trivial_copy_constructor;
            typedef __true_type    has_trivial_assignment_operator;
            typedef __true_type    has_trivial_destructor;
            typedef __true_type    is_POD_type;
            };
                  簡化<stl_algobase.h>中copy的部分代碼來說明對__type_traits的應(yīng)用。
            template<typename _Tp>
            inline _Tp* __copy_trivial(const _Tp* __first, const _Tp* __last, _Tp* __result)
            {
            memmove(__result, __first, sizeof(_Tp) * (__last - __first));
            return __result + (__last - __first);
            }
            template<typename _Tp>
            inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __true_type)
            return __copy_trivial(__first, __last, __result); } 
            template<typename _Tp>
            inline _Tp* __copy_aux (_Tp* __first, _Tp* __last, _Tp* __result, __false_type)
            { 另外處理;}
            template<typename _InputIter, typename _OutputIter> inline 
            _OutputIter copy (_InputIter __first, _InputIter __last, _OutputIter __result)
            {
            typedef typename iterator_traits<_InputIter>::value_type _ValueType;
            typedef typename __type_traits<_ValueType>::has_trivial_assignment_operator _Trivial;
            return __copy_aux(__first, __last, __result, _Trivial());
            }
                  Copy 函數(shù)利用__type_traits判斷當前的value_type是否有trival的賦值操作,如果是,則產(chǎn)生類__true_type的實例,編譯 時選擇__copy_trivial函數(shù)進行memmove,效率最高。如果是non-trival的賦值操作,則另作處理,效率自然低些。__true_type和__false_type之所以是類,就因為C++的函數(shù)重載是根據(jù)類型信息來的,不能依據(jù)參數(shù)值來判別。使用SGI STL時,可以為自己的類定義__type_traits顯式特化版本,以求達到高效率。
            本文來自CSDN博客,轉(zhuǎn)載請標明出處:http://blog.csdn.net/budTang/archive/2008/05/06/2397013.aspx

            posted @ 2012-12-01 19:57 鑫龍 閱讀(709) | 評論 (0)編輯 收藏

            僅列出標題
            共20頁: First 10 11 12 13 14 15 16 17 18 Last 
            日本久久久久亚洲中字幕| 亚洲精品无码久久久久AV麻豆| 久久精品亚洲AV久久久无码| 一级做a爰片久久毛片毛片| 国产亚洲精品久久久久秋霞| 国产成年无码久久久久毛片| 国产精品一区二区久久精品无码 | 久久精品无码一区二区三区日韩| 久久精品国产欧美日韩99热| 久久久久国产精品嫩草影院| 国产精品99久久99久久久| 欧美日韩中文字幕久久伊人| 亚洲va久久久久| 99久久精品免费| 好久久免费视频高清| 亚洲国产成人久久综合野外| 狠狠色婷婷综合天天久久丁香| 亚洲人AV永久一区二区三区久久| 精品午夜久久福利大片| 偷偷做久久久久网站| 久久久久国产一级毛片高清板| 国产精品99久久久精品无码| 大香网伊人久久综合网2020| 久久精品国产亚洲av麻豆图片 | 亚洲精品99久久久久中文字幕| 97久久国产综合精品女不卡| 日本高清无卡码一区二区久久| 久久99国产精品久久久| 婷婷伊人久久大香线蕉AV| 欧美一区二区久久精品| 四虎久久影院| 欧美色综合久久久久久| 精品乱码久久久久久夜夜嗨| 久久伊人精品青青草原高清| 精品久久久久久中文字幕人妻最新| 国产偷久久久精品专区| 亚洲色大成网站www久久九| 国产精品乱码久久久久久软件 | 久久免费精品一区二区| 精品国产福利久久久| 欧美久久综合性欧美|