• <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 - 32, comments - 42, trackbacks - 0, articles - 0
              C++博客 :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

            From 2008精選

            UNIX上C++程序設計守則(6)Add star

            準則6: 遵守多線程編程的常識


            1. 要準確把握在POSIX標準的函數中,那些函數是非線程安全的,一定不要使用
            2. 要讓自己編寫的函數符合線程安全
              • 在訪問共享數據/變量之前一定要先鎖定
              • 如果使用C++的話,一定要注意函數的同步方法

            說明: (2) 要讓自己編寫的函數符合線程安全


            在寫多線程的應用程序時,在多個線程里共享的變量要先鎖定然后在更新它.。那么在多線程里共享的變量主要有全局變量和函數內的靜態變量。而且,即使是short型和int型的共享變量也要先鎖定后更新才能保證其安全。


            ※ 詳細的是參考

            還有,在使用C++編程的場合要注意函數的方步方法。一般的說來下面的寫法是錯誤的。Mutex在函數內被聲明成靜態變量是不允許的

            int incr_counter(void) {
            static Mutex m; // 這么寫不行
            m.Lock();

            static int counter = 0;
            int ret = ++counter;

            m.Unlock();
            return ret;
            }

            應該用下面的方式來代替,

            Mutex m;

            int incr_counter(void) {
            m.Lock();
            // ...

            把Mutex聲明成全局變量的話比較好(稍微比上一個好)。


            ※ 詳細是參考

            UNIX上C++程序設計守則(6)-- 補記Add Star

            線程安全函數是像下面那樣

            1. 不要操作局部的靜態變量(函數內的static型的變量)和非局部的靜態數據(全局變量)。并且,其它的非線程安全函數不要調用
            2. 要操作這樣的變量的話, 就要使用mutex進行同步處理,來限制多個線程同時對它進行操作

            被定義的,但是

            • 特別是前者, 和被叫做可重入的(reentrant)函數有區別
            • 反之, 后者特別是和叫做"Serializable"(不單單是MT-Safe)"Safe"的函數有區別

            也有以上的情況。在Solaris的man手冊里, 用后者的方式進行區別. 從多線程程序里安全調用的話,就叫做"Safe", 而且, 在多線程中能夠并發(concurrency)地執行這個函數的處理的話,好像就叫做"MT-Safe"。

             

            嗯, 因為比較詳細的, 如果不是在對于執行速度要求比較苛刻的環境中編寫代碼的話, 單單地意識到「是否線程安全」就足夠了,不是嗎。

            posted @ 2009-02-01 15:41 lymons 閱讀(1469) | 評論 (1)編輯 收藏

            From 2008精選

            UNIX上C++程序設計守則(6)Add star

            準則6: 遵守多線程編程的常識


            1. 要準確把握在POSIX標準的函數中,那些函數是非線程安全的,一定不要使用
            2. 要讓自己編寫的函數符合線程安全
              • 在訪問共享數據/變量之前一定要先鎖定
              • 如果使用C++的話,一定要注意函數的同步方法

            說明: (1) 要準確把握那些非線程安全的函數,一定不要使用


            如果在POSIX平臺上進行多線程編程時,有幾個最基本的知識,也就是所說的“常識”,希望大家一定要嚴格遵守。


            ...首先、我們要理解“線程安全”的意思。線程安全的函數就是指,“一個能被在多個線程同時調用也不會發生問題的函數”。這樣的函數通常要滿足以下幾個的特質。

            1. 不要操作局部的靜態變量(函數內的static變量)和全局靜態數據(全局變量,函數外的靜態變量)。而且,也不要調用其他的非線程安全的函數
            2. 如果要操作這樣的變量的話,事先必須使用互斥鎖mutex進行同步,否則一定要限制多個線程同時對它的訪問

            那么、在POSIX標準的函數里面,也有不滿足上述條件的。由于歷史遺留問題,一些函數的識別標識(signature)的定義沒有考慮到線程安全的問題,所以不管怎么做都不能滿足上述的條件。例如,看看 localtime函數吧。它的定義的識別標識(signature)如下:

            struct tm *localtime(const time_t *timer);

            localtime 函數是,把一個用整數形式表示的時刻(從1970/1/1到現在為止的秒數)、轉換成一個能讓人容易明白的年月日形式表示出來的tm結構體并返回給調用者的函數。根據規格說明、返回出來的tm結構體是不需要free()掉,也不能釋放的。這個函數典型的實現就像下面的代碼那樣:

            struct tm *localtime(const time_t *timer) {
            static struct tm t;

            /* ... 從timer參數里算出年月日等數值 ... */

            t.tm_year = XXX;
            /* ...把它們填入到結構體內... */
            t.tm_hour = XXX;
            t.tm_min = XXX;
            t.tm_sec = XXX;

            return &t;
            }

            這個函數如果被像下面那樣使用的話,就會有漏洞:

            1. 在線程A里執行 ta = localtime(x);
            2. 在線程B里執行 tb = localtime(y);
            3. 線程A參照ta結構體里的數據 → 就發現這些數據是一些奇怪的值!

            ...在函數的說明手冊里對這個問題也沒有做過詳細的說明。關于這個漏洞,在localtime函數即使使用了mutex鎖也不能被回避掉。所以,這個函數定義的識別標識是不行滴。
            [譯者lymons注:在多個線程里調用localtime函數之所以有問題的原因是,localtime函數里返回的tm構造體是一個靜態的結構體,所以在線程A里調用localtime函數時,該結構體被賦予正確的值;而在線程A參照這個結構體之前,線程B又調用localtime的話,這個靜態的結構體又被賦予新的一個值。因此在線程A對這個結構體的訪問都是基于一個錯誤的值進行的]


            正因為如此,就像上面說過的POSIX規格(SUSv3)里整齊的

            asctime, basename, catgets, crypt, ctime, dbm_clearerr, dbm_close, dbm_delete, dbm_error, dbm_fetch, dbm_firstkey, dbm_nextkey, dbm_open, dbm_store, dirname, dlerror, drand48, ecvt, encrypt, endgrent, endpwent, endutxent, fcvt, ftw, gcvt, getc_unlocked, getchar_unlocked, getdate, getenv, getgrent, getgrgid, getgrnam,

            (省略)

            對于在規格中被定義為非線程安全的函數,應該制定一個避免使用它們的規則出來,并且制作一個能夠自動檢查出是否使用了這些函數的開發環境,應該是比較好的。


            反之,在這里沒有被登載的POSIX標準函數都被假定為 "shall be thread-safe" 的、所以在實際的使用中可以認為在多線程環境里是沒有問題的(而且在使用的平臺上沒有特別地說明它是非線程安全的話)。


            另外,有幾個非線程安全的函數,都準備了一個備用的線程安全版本的函數(僅僅是變更了函數的識別標識)。像這些函數為了與原版進行區別都在其函數名后面添加了 _r 這個后綴*1。例如,asctime函數就有線程安全版本的函數asctime_r。在規格說明中是否定義了備用函數,可以試著點擊剛才的那個網頁里面的函數名就可以看到。點擊 rand函數就可以看到,

            [TSF] int rand_r(unsigned *seed);

            用[TSF]這樣的文字標記出來的函數吧。這就是備用函數。在一覽中沒有記載出來的函數(備注: 稍微有點兒出入。請參照這里)、據我所知還有下面的備用函數。

            asctime_r, ctime_r, getgrgid_r, getgrnam_r, getpwnam_r, getpwuid_r, gmtime_r, localtime_r, rand_r, readdir_r, strerror_r, strtok_r

            還有,在規格以外,還準備了很多的下面那樣的函數。

            gethostbyname_r, gethostbyname2_r

            在最近的操作系統中、也使用 getaddrinfo API函數來解決IPv6名字對應的問題。gethostbyname系列的API都是比較陳舊的函數了、所以使用前面的函數還是比較好吧*2。根據規格SUSv3,getaddrinfo也是線程安全的:

            The freeaddrinfo() and getaddrinfo() functions shall be thread-safe.

            在多線程編程中,不要使用非線程安全的函數,而他們的備用函數可以放心地積極的去使用。


            后續

            *1:在C言語里函數不能重載,所以只能添加一個新的函數

            *2:跟網絡有關的API哪些是新的哪些是舊的,可以參考 IPv6網絡編程 (network technology series) 這本好書

            posted @ 2008-12-29 17:11 lymons 閱讀(2700) | 評論 (0)編輯 收藏

            From 2008精選

            [] UNIX上C++程序設計守則(5)Add star


            準則5: 盡可能避免線程中做延遲撤銷的處理

            • 線程的異步撤消是指:一個線程發出中斷其他線程的處理的一個動作
            • 延遲撤消因為是規格自由度比較高、所以根據OS和C庫函數的版本它也有各式各樣的動作
              • 要想在不同的環境下都能穩定的動作的話,就必須要詳細調查運行環境和,對C庫函數進行抽象化,做必要的條件編譯
              • 在C++中、「撤消發生時的對象釋放」的實現不具有可移植性
            • 線程撤銷要慎重使用。在C++里不要使用

            說明:


            在前面我們已經講過,線程的撤消分為「異步」「延遲」這兩種類型、并且「異步撤消」也是非常容易引起各種復雜問題的元兇。


            那么,現在要在程序中除掉「延遲撤消」。延遲撤消雖然不會像異步撤消那樣會引起各種各樣的問題、但是、注意事項還是有很多的。只有把下面的這些注意事項全部都把握之后才能放心使用。


            注意事項1: 要好好把握撤消點


            和異步撤消不一樣的是、撤消處理一直會被延遲到在代碼上明示出來的撤消點之后才會被執行。如果編寫了一個具有延遲撤消可能的代碼、代碼中的那條語句是撤消點、必須要正確的把握。


            首先、調用過pthread_testcancel函數的地方就變成撤消點了。當然這個函數是、僅僅為了「變成延遲撤消」的目的而設置出來的函數。除此之外、某些標準庫函數被調用后會不會變成撤消點是在規格(SUSv3)中決定的。請參照規格說明、有下面的函數一覽。


            下面的函數撤消點

            accept, aio_suspend, clock_nanosleep, close, connect, creat, fcntl, fdatasync,
            fsync, getmsg, getpmsg, lockf, mq_receive, mq_send, mq_timedreceive,
            mq_timedsend, msgrcv, msgsnd, msync, nanosleep, open, pause, poll, pread,
            pselect, pthread_cond_timedwait, pthread_cond_wait, pthread_join,
            pthread_testcancel, putmsg, putpmsg, pwrite, read, readv, recv, recvfrom,
            (略)
            下面的函數不是撤消點

            access, asctime, asctime_r, catclose, catgets, catopen, closedir, closelog,
            ctermid, ctime, ctime_r, dbm_close, dbm_delete, dbm_fetch, dbm_nextkey, dbm_open,
            dbm_store, dlclose, dlopen, endgrent, endhostent, endnetent, endprotoent,
            endpwent, endservent, endutxent, fclose, fcntl, fflush, fgetc, fgetpos, fgets,
            fgetwc, fgetws, fmtmsg, fopen, fpathconf, fprintf, fputc, fputs, fputwc, fputws,
            (略)

            看到這些我想已經明白了、但是在規格中也說明了「能否成為撤消點跟具體的實現相關的函數」也是多數存在的。原因是、為了可移植性、保證「在一定的時間內讓線程的延遲撤消完成」是很困難的事情*1。做的不好的話、只要稍微一提升OS的版本就可能讓做出來的程序產品不能動作。


            即使是這樣那還想要使用延遲撤消嗎?


            注意事項2: 實現要知道cleanup函數的必要性


            可能被延遲撤銷的線程在運行的過程中,要申請資源的場合,一定要考慮到以下的幾點,否則就會編制出含有資源丟失和死鎖的軟件產品。


            例如編寫的下面的函數就不能被安全的延遲撤銷掉。

            void* cancel_unsafe(void*) {
            static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
            pthread_mutex_lock(&mutex); // 此處不是撤消點
            struct timespec ts = {3, 0}; nanosleep(&ts, 0); // 經常是撤消點
            pthread_mutex_unlock(&mutex); // 此處不是撤消點
            return 0;
            }
            int main(void) {
            pthread_t t;
            // pthread_create后馬發上收到一個有效的延遲撤消的要求
            pthread_create(&t, 0, cancel_unsafe, 0);
            pthread_cancel(t);
            pthread_join(t, 0);
            cancel_unsafe(0); // 發生死鎖!
            return 0;
            }

            在上面的樣例代碼中、nanosleep執行的過程中經常會觸發延遲撤銷的最終動作,但是這個時候的mutex鎖還處于被鎖定的狀態。而且、線程一被延遲撤消的話就意味著沒有人去釋放掉這個互斥鎖了*2。因此、在下面的main函數中調用同樣的cancel_unsafe函數時就會引起死鎖了。


            為了回避這個問題、利用pthread_cleanup_push函數在撤消時釋放掉互斥鎖的話就OK了,也就不會死鎖了。

            // 新增清除函數
            void cleanup(void* mutex) {
            pthread_mutex_unlock((pthread_mutex_t*)mutex);
            }

            // 粗體字部分是新增的語句
            void* cancel_unsafe(void*) {
            static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
            pthread_cleanup_push(cleanup, &mutex);
            pthread_mutex_lock(&mutex);
            struct timespec ts = {3, 0}; nanosleep(&ts, 0);
            pthread_mutex_unlock(&mutex);
            pthread_cleanup_pop(0);
            return 0;
            }

            注意事項3: 實現要清楚延遲撤消和C++之間的兼容度


            使用C語言的場合,利用上面的pthread_cleanup_push/pop函數就能安全地執行延遲撤消的動作,但是在C++語言的場合就會出現其他的問題。C++與延遲撤消之間的兼容度是非常差的。具體的表現有以下兩個問題:


            1. 執行延遲撤消的時候,內存棧上的對象的析構函數會不會被調用跟具體的開發環境有關系
              • GCC3版本就不會調用。
              • Solaris和Tru64 UNIX下的原生編譯器的場合,就調用析構函數(好像)
            2. pthread_cleanup_push/pop函數和C++的異常處理機制之間有著怎樣的相互影響也能具體環境有關

            不調用析構函數,或者在拋出異常的時候不能做cleanup處理,經常是發生內存泄漏,資源丟失,程序崩潰,死鎖等現象的原因。令人意外的是對于這個深層次的問題,就連Boost C++庫都束手無策。

            [Q] Why isn't thread cancellation or termination provided?

            [A] There's a valid need for thread termination, so at some point Boost.Threads probably will include it, but only after we can find a truly safe (and portable) mechanism for this concept.

            先必須確保對象的自由存儲,而后全都讓cleanup函數去釋放對象的方法也有,但是這次是犧牲了異常安全性。
            (原文沒有看明白:オブジェクトを必ずフリーストア上に確保し、解體を全て、クリーンナップハンドラに行わせる手もありますが、今度は例外安全性が犠牲になるでしょう。)


            應該說的是,在使用C++的工程里不對線程進行延遲撤消處理還是比較實際的。

            *1:好的問題是 gethostbyname()函數

            *2:異步撤消跟malloc函數的例子很相似

            原文地址:http://d.hatena.ne.jp/yupo5656/20040725/p2

            posted @ 2008-12-25 14:08 lymons 閱讀(1322) | 評論 (0)編輯 收藏

            From 2008精選

            原文地址:http://d.hatena.ne.jp/yupo5656/20040724/p1

            [] UNIX上的C++程序設計守則 (4)Add star

            鐵則4: 請不要做線程的異步撤消的設計

            • 線程的異步撤銷是指: 某個線程的執行立刻被其他線程給強制終止了
            • 請不要單單為了讓“設計更簡單”或者“看起了更簡單”而使用線程的異步撤消

            咋一看還是挺簡單的。但是搞不好可能會引起各種各樣的問題。請不要在不能把握問題的實質就做出使用線程的異步撤消的設計!

            在pthread的規格說明中,允許一個線程可以強制中斷某個線程的執行。這就是所說的異步撤消。


            線程的撤消有下面的兩種方式。

            • 方式1: 異步撤消(PTHREAD_CANCEL_ASYNCHRONOUS)
              • 撤銷動作是馬上進行的
            • 方式2: 延遲撤銷(PTHREAD_CANCEL_DEFERRED)
              • 撤消動作,是讓線程的處理一直被延遲到撤消點才會去執行

            還有,到底是用哪種撤消方式,不是撤消側,而是被撤銷側能夠決定的*1。另外,在被撤銷側也能夠選擇完全禁止撤消的這種方式 *2


            會造成什么問題呢

            那么,讓我看看亂用線程的異步撤消會引起什么問題呢。看過準則3的人可能會知道,在下面的腳本里,被撤銷線程以外的任意一個線程會被死鎖。

            1. 線程1中調用malloc函數正在做內存分配的過程中,線程2異步撤消了線程1的處理
            2. 線程1馬上被撤銷,但是malloc函數中的互斥鎖就沒有線程去解除了
            3. 后面的任意一個線程如果再次調用malloc函數的話就會馬上導致該線程死鎖

            在這個例子中使用了malloc函數,但是其他的危險函數還有很多。


            反之,即使做了異步撤消也沒有問題的函數也有少數存在的、我們把它們叫做「async-cancel safe函數」或者「異步撤消安全函數」。在一些商用UNIX*3中、OS提供的api函數的文檔說明中有async-cancel safety的記載、但是在Linux(glibc)里就很遺憾,幾乎沒有相關的說明。


            在這兒,參看規格(SUSv3)的話,會發現,、描述異步撤消安全的函數只有3個

            1. pthread_cancel
            2. pthread_setcancelstate
            3. pthread_setcanceltype

            而且、里面還有"No other functions are required to be async-cancel-safe"這樣的記載。因此,Linux的場合,如果在文檔里沒有記載成async-cancel safety的函數,我們還是把它假定成不安全的函數為好!


            如何避免這些問題呢

            在多線程編程中為了安全的使用異步撤消處理、有沒有回避死鎖的方法呢?我們試著想了幾個。他們與準則3里的線程+fork的場合的回避策很相似。


            回避方法1: 被撤銷線程中,只能使用異步撤消安全函數


            首先,被撤銷線程中,只能使用異步撤消安全函數。但是這個方法

            • 在規格說明中只有3個異步撤消安全的函數
            • 這些以外的函數是不是異步撤消安全(商用UNIX)、因為沒有說明文檔我們不清楚(Linux)

            中有以上的兩點,所以這個回避方法幾乎不現實。


            回避方法2: 被撤銷線程中,在做非異步撤消安全處理的過程中,再把撤消方式設置成「延遲」或者是「禁止」


            第二個是,被撤銷線程在做非異步撤消安全處理的過程中,把撤消方式再設定成「延遲」或者「禁止」。對于這個方法

            • 就像方法1寫的那樣、要把我那個函數是異步撤消安全的一時還是挺麻煩的
            • 在任意的場所并不能保證撤消動作會被馬上執行
              • 例如,再設定成「延遲」后的一段時間內如果撤消發生時、某個正在阻塞的I/O函數是否能夠被解除阻塞還是挺微妙的
              • 如果設定成撤消禁止的話,則撤消會被屏蔽掉

            有上面樣的問題、會導致「一精心設計撤消方式的替換,從一開始就使用延遲撤消還不夠好」這樣的結果。所以這幾乎是不好的一個回避策。


            回避方法3: 使用pthread_cleanup_push函數,登錄異步撤消時的線程數據清除的回調函數


            第三種則是,用pthread_cleanup_push函數、登錄一個在異步撤消發生時的數據清除的回調函數。這和在準則3中介紹的pthread_atfork函數有點兒類似。用這個函數登錄的回調函數來清除線程的數據和鎖,就可以回避死鎖了。


            ...但是,pthread_cleanup_push函數登錄的回調函數,在「延遲撤消」的場合是不能被調用的。因此、這個回避方法對于異步撤消沒有什么大的作用。


            回避方法4: 不要執行異步撤消處理


            最后是、不要執行異步撤消處理。反而代之的是、

            • 設計成不依賴使用異步撤消那樣的處理

            或者

            • 不得不使用線程撤消的話、不做異步撤消而作延遲撤消的處理

            這是比較實際的做法,是我們值得推薦的。

            *1:pthread_setcanceltype函數

            *2:pthread_setcancelstate函數

            *3:Solaris和HP-UX等

            posted @ 2008-12-19 11:01 lymons 閱讀(1270) | 評論 (0)編輯 收藏

                 摘要: Linux中處理來自共享對象的同步事件 怎么利用設計模式來更有效的使用共享內存   級別:中等  Sachin Agrawal (sachin_agrawal@in.ibm.com), Senior Software Engineer, IBM Software Labs, IndiaSwati P. Udas (swatudas@in.ibm.com), Softw...  閱讀全文

            posted @ 2008-06-01 21:41 lymons 閱讀(874) | 評論 (0)編輯 收藏

            From 2008精選

            [C++] UNIX上的C++程序設計守則(1)

            原文:http://d.hatena.ne.jp/yupo5656/20040712/p1

            Unix跟Windows等那些”對于開發者易于使用”的OS比起來,在信號和線程的利用方面有諸多的限制。但是即使不知道這些知識就做構架設計和實現的情況也隨處可見。這個就是那些經常不能再現的bug的溫床吧。

            因此,我想分成幾回來寫一些準則來防止陷入到這些圈套里。

            準則1:不依賴于信號收發的設計

            ·給其他進程以及自己發送異步信號并改變處理流程的設計不要做

            •  異步信號是值用kill系統調用來創建?發送的信號、例如SIGUSR1,SIGUSR2,SIGINT,SIGTERM 等
            • 簡單的使用忽略信號(SIG_IGN)則沒有問題

            ·不要把線程和信號一起使用 

            • 這將使程序動作的預測和調試變得很困難

             

            說明:

            同步信號是指,因為某些特定的操作*1而引起向自身進程發送某些特定的信號,例如SIGSEGV,SIGBUS,SIGPIPE,SIGSYS,SIGILL,SIGFPE。異步信號就是這些以外的信號。在什么時機發送異步信號并不能被預測出來。我們會在程序里追加收到某些信號時做一些特殊處理(信號處理函數)的函數。那么根據收到的信號就跳到信號處理函數的程序就叫做”在任意代碼處都能發生跳轉”的程序。這樣的程序往往隱藏這下面的那些問題:

            1. 容易引入BUG。”任意的代碼”雖然也包含”執行C/C++里面的一條語句的過程中”的意思,但這很容易跳出程序員的正常思維以及默認的假定條件。編寫程序的時候往往需要考慮比C++異常分支還要多得多的分支情況。
            2. 使測試項目激增。即使根據白盒測試達成100%的分支覆蓋,也不能網羅到因為接受信號而發生的跳轉分支處理。也就是說做到100%的網羅信號跳轉分支的測試是不能全部實現的。一般的,加上要考慮” 在實行某個特定代碼時因為接受到信號而發生的誤操作”這樣的BUG會經常發生*2的這種情況,測試困難往往就是導致軟件的品質低下的誘因。

             

            根據經驗,”當檢查到子進程結束(接收到SIGCHLD信號)時,要做必要的處理”像這樣的信號處理不管做什么都是有必要的情況會有,但是除此以外的信號處理,例如

            • 把自己的狀態用信號告訴其他進程
            • 主線程在輸入輸出函數里發送信號給被阻塞的子線程,并解除阻塞

            等,是應該事先好好好好考慮過后再去做實際的實現。前者的話,如果不強制在”普通的”進程間進行通信的話可能會很好,后者是特意要使用線程,也要應該按照即使阻塞了也不能發生問題那樣再設計。

            不管怎么樣,如果必須要使用信號的話,也要先全部*3理解這些陷阱以及,和多線程軟件設計的場合一樣或者說比它更嚴格的制約.注意事項都需要銘記在心里。

            *1:例如,引用空指針

            *2:參照 id:yupo5656:20040703 的sigsafe說明

            *3:暫時先掌握”準則2”:-)

            posted @ 2008-06-01 20:27 lymons 閱讀(1376) | 評論 (0)編輯 收藏

            From 2008精選

            UNIXC++程序設計守則 (2)
            原文地址:http://d.hatena.ne.jp/yupo5656/20040712/p2

            準則2: 要知道信號處理函數中可以做那些處理
            · 在用sigaction函數登記的信號處理函數中可以做的處理是被嚴格限定的
            · 僅僅允許做下面的三種處理
               1. 局部變量的相關處理
               2. “volatile sig_atomic_t”類型的全局變量的相關操作
               3. 調用異步信號安全的相關函數
            · 以外的其他處理不要做

             
            說明
            因為在收到信號時要做一些處理,那通常是準備一個信號處理函數并用sigaction函數把它和信號名進行關聯的話就OK了。但是,在這個信號處理函數里可以做的處理是像上面那樣被嚴格限定的。沒有很好掌握這些知識就隨便寫一些代碼的話就會引起下面那樣的問題
            · 問題1: 有程序死鎖的危險
               o  這是那些依賴于某一時刻,而且錯誤再現比較困難的BUG產生的真正原因
               o  死鎖是一個比較典型的例子,除此之外還能引起函數返回值不正確,以及在某一函數內執行時突然收到SEGV信號等的誤操作。
               譯者注1SEGV通常生在試圖訪問無效內存區域(可能是個NULL,或超出程空之外的內存地址)。當bug原因和SEGV影響在不同時間現時,它別難于捕到。


            · 問題2: 由于編譯器無意識的優化操作,有導致程序紊亂的危險
               o  這是跟編譯器以及編譯器優化級別有關系的bug。它也是“編譯器做了優化處理而不能正常動作”,“因為inline化了程序不能動作了”,“變換了OS了程序也不能動作”等這些解析困難bug產生的原因。

             
            還是一邊看具體的代碼一邊解說吧。在下面的代碼里至少有三個問題,根據環境的不同很可能引起不正確的動作*1按照次序來說明里面的錯誤

             

             1int gSignaled;
             2void sig_handler(int signo) {
             3    std::printf("signal %d received!\n", signo);
             4    gSignaled = 1;
             5}

             6int main(void{
             7    struct sigaction sa;
             8  // (省略)
             9  sigaction(SIGINT, &sa, 0);
            10    gSignaled = 0;
            11    while(!gSignaled) {
            12  //std::printf("waiting\n");
            13        struct timespec t = 10 }; nanosleep(&t, 0);
            14    }

            15}

            16

             

            錯誤1: 競爭條件
                在上面的代碼里有競爭條件sigaction函數被調用后gSignaled還未被賦值成0值之前,如果接受到SIGINT信號了那會變得怎么樣呢? 在信號處理函數中被覆寫成1后的gSignaled會在信號處理函數返回后被初始化成0在后面的while循環里可能會變成死循環


            錯誤2: 全局變量gSignaled 聲明的類型不正確
                 在信號處理函數里使用的全局變數gSignaled的類型沒有聲明成volatile sig_atomic_t 這樣的話在執行while循環里的代碼的時候接收到了了SIGINT信號時有可能引起while的死循環那為什么能引起這樣的情況呢
                · 信號處理函數里,把內存上gSignaled的值變更成1 ,它的匯編代碼如下:

             

                      movl    $1, gSignaled

                · 但是,就像下面的代碼描述的那樣,main函數是把gSignaled的值存放到了寄存器里while循環之前,僅僅是做了一次拷貝變量gSignaled內存上的值到寄存器里而在while循環里只是參照這個寄存器里的值
                      movl     gSignaled, %ebx
                   .L8:
                                   testl    %ebx, %ebx
                                   jne      .L8

                 在不執行優化的情況下編譯后編譯器有可能不會生成上面那樣的偽代碼Gcc當使用-O2選項做優化編譯時,生成的實際那樣的匯編代碼產生的危害并不僅僅是像上面說的威脅那樣簡單。這方面的問題,是設備驅動的開發者所要知道的常識,但現實情況是對于應用程序的設計者.開發者幾乎都不知道這些知識。
            為了解決上面的問題,全局變量gSignaled的類型要像下面那樣聲明

                 volatile sig_atomic_t gSignaled;

                 volatile則是提示編譯器不要像上面那樣做優化處理,變成每次循環都要參照該變量內存里的值那樣進行編譯。所以在信號處理函數里把該變量的值修改后也能真實反映到main函數的while循環里
            sig_atomic_t 是根據CPU類型使用typedef來適當定義的整數值,例如x86平臺是int就是指用一條機器指令來更新內存里的最大數據*2在信號處理函數里要被引用的變量必須要定義成sig_atomic_t類型那么不是sig_atomic_t類型的變量(比如x86平臺上的64位整數)就得使用兩條機器指令來完成更新動作。如果在執行一條機器指令的時候突然收到一個信號而程序執行被中斷,而且在信號處理函數中一引用這個變量的話,就只能看到這個變量的部分的值。另外,由于字節對齊的問題不能由一條機器指令來完成的情況也會存在。把該變量的類型變成sig_atomic_t的話,這個變量被更新時就只需要一條機器指令就可以完成了。所以在信號處理函數里即使使用了該變量也不會出現任何問題

                 2006/1/16 補充: 有一點東西忘記寫了關于sig_atomic_t詳細的東西,請參考C99規范的§7.14.1.1/5小節在信號處理函數里對volatile sig_atomic_t以外的變量進行修改,其結果都是"unspecified"(參照譯者注2)另外, sig_atomic_t類型的變量的取值范圍是在SIG_ATOMIC_MIN/MAX之間 (參見§7.18.3/2)有無符號是跟具體的實現有關。考慮到移植性取值在0127之間是比較合適的C99也支持這個取值范圍C++規范(14882:2003)里也有同樣的描述確切的位置是§1.9/9這里SUSv3的相關描述請參考sigaction這里*3此外雖然在GCC的參考手冊里也說了把指針類型更新成原子操作,但在標準C/C++卻沒有記載*4
            譯者注2
                       When the processing of the abstract machine is interrupted by receipt of a signal, the value of objects with type other than volatile sig_atomic_t are unspecified, and the value of any object not of volatile sig_atomic_t that is modified by the handler becomes undefined.
                                   ------
            ISO/IEC FDIS 14882:1998(E) 1.9小節


            錯誤3: 在信號處理函數里調用了不可重入的函數
            上述的樣例代碼中調用了printf函數,但是這個函數是一個不可重入函數,所以在信號處理函數里調用的話可能會引起問題。具體的是,在信號處理函數里調用printf函數的瞬間,引起程序死鎖的可能性還是有的。但是,這個問題跟具體的時機有關系,所以再現起來很困難,也就成了一個很難解決的bug了。
            下面講一下bug發生的過程。首先講解一下printf函數的內部實現。
                · printf函數內部調用malloc函數
                · malloc函數會在內部維護一個靜態區域來保存mutex是為了在多線程調用malloc函數的時候起到互斥的作用
                · 總之malloc函數里有“mutex鎖定,分配內存,mutex解鎖”這樣“連續的不能被中斷”的處理

             

            main関數:
              call printf  // while循環中的printf函數
                call malloc
                  call pthread_mutex_lock(鎖定malloc函數內的靜態
            mutex)
                  // malloc處理時
            ..
            收到SIGINT信號

                    call sig_handler
                      call printf // 信號處理函數中的printf函數

                        call malloc
                          call pthread_mutex_lock(鎖定malloc函數內的靜態
            mutex)
                          // 相同的mutex一被再度鎖定,就死鎖啦!!

             
                 知道上面的流程的話像這樣的由于信號中斷引起的死鎖就能被理解了吧。為了修正這個bug,在信號處理函數里就必須調用可重入函數。可重入函數的一覽表在UNIX規范 (SUSv3)有詳細記載*5你一定會驚訝于這個表里的函數少吧。
            另外,一定不要忘記以下的幾點:
                · 雖然在SUSv3里有異步信號安全(async-signal-safe)函數的一覽,但根據不同的操作系統,某些函數是沒有被實現的。所以一定要參考操作系統的手冊
                · 第三者做成的函數,如果沒有特別說明的場合,首先要假定這個函數是不可重入函數,不能隨便在信  號處理函數中使用。
                · 調用不可重入函數的那些函數就會變成不可重入函數了


                最后,為了明確起見,想說明一下什么是異步信號安全(async-signal-safe)”函數異步信號安全函數是指在該函數內部即使因為信號而正在被中斷,在其他的地方該函數再被調用了也沒有任何問題。如果函數中存在更新靜態區域里的數據的情況(例如,malloc),一般情況下都是不全的異步信號函數。但是,即使使用靜態數據,如果在這里這個數據時候把信號屏蔽了的話,它就會變成異步信號安全函數了。
            譯者注3:不可重入函數就不是異步信號安全函數

             


            *1sigaction函數被調用前,一接收到SIGINT信號就終止程序,暫且除外吧
            *2“最大”是不完全正確的例如,Alpha平臺上32/64bit的變量用一條命令也能被更新,但是好像把8/16bit的數據更新編程了多條命令了http://lists.sourceforge.jp/mailman/archives/anthy-dev/2005-September/002336.html 請參考這個URL地址
            *3If the signal occurs other than as the result of calling abort(), kill(), or raise(), the behavior is undefined if the signal handler calls any function in the standard library other than one of the functions listed in the table above or refers to any object with static storage duration other than by assigning a value to a static storage duration variable of type volatile sig_atomic_t. Furthermore, if such a call fails, the value of errno is unspecified.
            *4在這個手冊里“ In practice, you can assume that int and other integer types no longer than int are atomic. ”這部分是不正確的請參照Alpha的例子
            *5The following table defines a set of functions that shall be either reentrant or non-interruptible by signals and shall be async-signal-safe. 后面有異步信號安全函數一覽

            posted @ 2008-06-01 20:22 lymons 閱讀(2261) | 評論 (0)編輯 收藏

                 摘要: From 2008精選 鉄則3: マルチスレッドのプログラムでのforkはやめよう準則3:多線程程序里不準使用fork   マルチスレッドのプログラムで、「自スレッド以外のスレッドが存在している狀態」でfork   何が起きるか能引起什么問題呢?   実例から見てみましょう。次のコードを実行すると、子プロセスは実行...  閱讀全文

            posted @ 2008-06-01 20:16 lymons 閱讀(13055) | 評論 (0)編輯 收藏

                 摘要: 隱鋒同學的blog上有關于libxml2的一篇文章,正好最近要使用這個庫來處理xml文件。
            不過在測試時我們發現用文章里F. 添加屬性例程代碼 時,添加的keyword結點后面沒有回車,
            跟后面的結點擠在一行了,不是很好看。
            例如,以下的xml例子文件  閱讀全文

            posted @ 2007-11-30 13:44 lymons 閱讀(10132) | 評論 (8)編輯 收藏

            在讀取配置文件的每行內容的時候,需要去掉字符串中的空格或則tab鍵,
            在C里實現它還是比較容易,但要是用string類來存儲每行字符串的時候,
            可能就不會象C那樣容易處理字符串的每個字符。
            為了處理方便,利用遞歸來去掉string的所有空格字符:
            1string& trim(string &str, string::size_type pos = 0)
            2{
            3    static const string delim = " \t"; //刪除空格或者tab字符
            4    pos = str.find_first_of(delim, pos);
            5    if (pos == string::npos)
            6        return str;
            7    return trim(str.erase(pos, 1));
            8}
            因為配置文件里每行的內容不會很多,使用遞歸來講可能也不會
            對整體程序有明顯的降低。

            如果只是去除兩端的空格字符則比較簡單一些,網絡到處都有它相關的例子:
            1string trimEnd(string &str)
            2{
            3    const string &delim =" \t" ;
            4    string r=str.erase(str.find_last_not_of(delim)+1);
            5    return r.erase(0,r.find_first_not_of(delim));
            6}

            posted @ 2007-11-28 11:56 lymons 閱讀(19086) | 評論 (20)編輯 收藏

            僅列出標題
            共4頁: 1 2 3 4 
            我的個人簡歷第一頁 我的個人簡歷第二頁
            久久精品国产第一区二区三区| 欧美日韩精品久久久久| 色综合久久久久无码专区| 久久久久久久久无码精品亚洲日韩 | 91麻精品国产91久久久久| 狠狠色丁香婷婷综合久久来来去| 人妻丰满?V无码久久不卡| 亚洲国产一成人久久精品| 91超碰碰碰碰久久久久久综合| 日本加勒比久久精品| 久久er99热精品一区二区| 久久综合色区| 99久久精品免费看国产| 欧美日韩精品久久久免费观看| 久久综合九色综合久99| 伊人久久大香线蕉AV色婷婷色| 日本精品久久久中文字幕| 精品综合久久久久久97| 国产A级毛片久久久精品毛片| 午夜精品久久久久久久| 亚洲精品高清一二区久久| 精品久久久久久国产91| 新狼窝色AV性久久久久久| 一级a性色生活片久久无少妇一级婬片免费放 | 国产精品久久久久…| 免费久久人人爽人人爽av| 久久久网中文字幕| 国产99久久久久久免费看| 国产精品一区二区久久| 亚洲香蕉网久久综合影视| 久久久综合香蕉尹人综合网| AA级片免费看视频久久| 精品久久人妻av中文字幕| 亚洲精品国精品久久99热一| 精品久久久中文字幕人妻| 一级a性色生活片久久无| 一本大道久久东京热无码AV | 一本综合久久国产二区| 久久免费香蕉视频| 中文字幕无码久久人妻| 狠狠色丁香婷婷久久综合 |