青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品

桃源谷

心靈的旅行

人生就是一場旅行,不在乎旅行的目的地,在乎的是沿途的風景和看風景的心情 !
posts - 32, comments - 42, trackbacks - 0, articles - 0
  C++博客 :: 首頁 :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

From 2008精選

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

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


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

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


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


※ 詳細的是參考

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

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

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

m.Unlock();
return ret;
}

應(yīng)該用下面的方式來代替,

Mutex m;

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

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


※ 詳細是參考

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

線程安全函數(shù)是像下面那樣

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

被定義的,但是

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

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

 

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

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

From 2008精選

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

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


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

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


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


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

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

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

struct tm *localtime(const time_t *timer);

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

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

/* ... 從timer參數(shù)里算出年月日等數(shù)值 ... */

t.tm_year = XXX;
/* ...把它們填入到結(jié)構(gòu)體內(nèi)... */
t.tm_hour = XXX;
t.tm_min = XXX;
t.tm_sec = XXX;

return &t;
}

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

  1. 在線程A里執(zhí)行 ta = localtime(x);
  2. 在線程B里執(zhí)行 tb = localtime(y);
  3. 線程A參照ta結(jié)構(gòu)體里的數(shù)據(jù) → 就發(fā)現(xiàn)這些數(shù)據(jù)是一些奇怪的值!

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


正因為如此,就像上面說過的POSIX規(guī)格(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,

(省略)

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


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


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

[TSF] int rand_r(unsigned *seed);

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

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

還有,在規(guī)格以外,還準備了很多的下面那樣的函數(shù)。

gethostbyname_r, gethostbyname2_r

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

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

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


后續(xù)

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

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

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

From 2008精選

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


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

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

說明:


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


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


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


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


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


下面的函數(shù)撤消點

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,
(略)
下面的函數(shù)不是撤消點

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,
(略)

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


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


注意事項2: 實現(xiàn)要知道cleanup函數(shù)的必要性


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


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

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

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


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

// 新增清除函數(shù)
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: 實現(xiàn)要清楚延遲撤消和C++之間的兼容度


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


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

不調(diào)用析構(gòu)函數(shù),或者在拋出異常的時候不能做cleanup處理,經(jīng)常是發(fā)生內(nèi)存泄漏,資源丟失,程序崩潰,死鎖等現(xiàn)象的原因。令人意外的是對于這個深層次的問題,就連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函數(shù)去釋放對象的方法也有,但是這次是犧牲了異常安全性。
(原文沒有看明白:オブジェクトを必ずフリーストア上に確保し、解體を全て、クリーンナップハンドラに行わせる手もありますが、今度は例外安全性が犠牲になるでしょう。)


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

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

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

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

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

From 2008精選

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

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

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

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

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

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


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

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

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


會造成什么問題呢

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

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

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


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


在這兒,參看規(guī)格(SUSv3)的話,會發(fā)現(xiàn),、描述異步撤消安全的函數(shù)只有3個

  1. pthread_cancel
  2. pthread_setcancelstate
  3. pthread_setcanceltype

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


如何避免這些問題呢

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


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


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

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

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


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


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

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

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


回避方法3: 使用pthread_cleanup_push函數(shù),登錄異步撤消時的線程數(shù)據(jù)清除的回調(diào)函數(shù)


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


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


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


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

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

或者

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

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

*1:pthread_setcanceltype函數(shù)

*2:pthread_setcancelstate函數(shù)

*3:Solaris和HP-UX等

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

     摘要: Linux中處理來自共享對象的同步事件 怎么利用設(shè)計模式來更有效的使用共享內(nèi)存   級別:中等  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 閱讀(897) | 評論 (0)編輯 收藏

From 2008精選

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

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

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

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

準則1:不依賴于信號收發(fā)的設(shè)計

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

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

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

  • 這將使程序動作的預(yù)測和調(diào)試變得很困難

 

說明:

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

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

 

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

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

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

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

*1:例如,引用空指針

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

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

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

From 2008精選

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

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

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


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

 
還是一邊看具體的代碼一邊解說吧。在下面的代碼里至少有三個問題,根據(jù)環(huán)境的不同很可能引起不正確的動作*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函數(shù)被調(diào)用后gSignaled還未被賦值成0值之前,如果接受到SIGINT信號了那會變得怎么樣呢? 在信號處理函數(shù)中被覆寫成1后的gSignaled會在信號處理函數(shù)返回后被初始化成0在后面的while循環(huán)里可能會變成死循環(huán)


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

 

          movl    $1, gSignaled

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

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

     volatile sig_atomic_t gSignaled;

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

     2006/1/16 補充: 有一點東西忘記寫了關(guān)于sig_atomic_t詳細的東西,請參考C99規(guī)范的§7.14.1.1/5小節(jié)在信號處理函數(shù)里對volatile sig_atomic_t以外的變量進行修改,其結(jié)果都是"unspecified"(參照譯者注2)另外, sig_atomic_t類型的變量的取值范圍是在SIG_ATOMIC_MIN/MAX之間 (參見§7.18.3/2)有無符號是跟具體的實現(xiàn)有關(guān)。考慮到移植性取值在0127之間是比較合適的C99也支持這個取值范圍C++規(guī)范(14882:2003)里也有同樣的描述確切的位置是§1.9/9這里SUSv3的相關(guān)描述請參考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小節(jié)


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

 

main関數(shù):
  call printf  // while循環(huán)中的printf函數(shù)
    call malloc
      call pthread_mutex_lock(鎖定malloc函數(shù)內(nèi)的靜態(tài)
mutex)
      // malloc處理時
..
收到SIGINT信號

        call sig_handler
          call printf // 信號處理函數(shù)中的printf函數(shù)

            call malloc
              call pthread_mutex_lock(鎖定malloc函數(shù)內(nèi)的靜態(tài)
mutex)
              // 相同的mutex一被再度鎖定,就死鎖啦!!

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


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

 


*1sigaction函數(shù)被調(diào)用前,一接收到SIGINT信號就終止程序,暫且除外吧
*2“最大”是不完全正確的例如,Alpha平臺上32/64bit的變量用一條命令也能被更新,但是好像把8/16bit的數(shù)據(jù)更新編程了多條命令了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. 后面有異步信號安全函數(shù)一覽

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

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

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

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

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

在讀取配置文件的每行內(nèi)容的時候,需要去掉字符串中的空格或則tab鍵,
在C里實現(xiàn)它還是比較容易,但要是用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}
因為配置文件里每行的內(nèi)容不會很多,使用遞歸來講可能也不會
對整體程序有明顯的降低。

如果只是去除兩端的空格字符則比較簡單一些,網(wǎng)絡(luò)到處都有它相關(guān)的例子:
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 閱讀(19140) | 評論 (20)編輯 收藏

僅列出標題
共4頁: 1 2 3 4 
我的個人簡歷第一頁 我的個人簡歷第二頁
青青草原综合久久大伊人导航_色综合久久天天综合_日日噜噜夜夜狠狠久久丁香五月_热久久这里只有精品
  • <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>
            久久国内精品视频| 猫咪成人在线观看| 久久精品国产清高在天天线| 欧美激情中文字幕一区二区| 欧美亚洲免费电影| 久久av在线看| 久久亚洲捆绑美女| 欧美黑人多人双交| 国产拍揄自揄精品视频麻豆| 亚洲精品女人| 一区二区三区免费观看| 亚洲四色影视在线观看| 亚洲欧美在线一区| 老司机成人在线视频| 亚洲电影欧美电影有声小说| 亚洲电影激情视频网站| 一区二区三区日韩| 久久xxxx| 欧美激情综合五月色丁香| 欧美午夜在线一二页| 国产在线精品二区| 日韩视频在线免费| 久久精品视频在线免费观看| 欧美激情第1页| 99热在线精品观看| 久久精品视频在线播放| 欧美日韩123| 精品999在线播放| 亚洲性感美女99在线| 久久久久久久久久久久久9999 | 欧美影院视频| 免费日韩视频| 国产麻豆午夜三级精品| 亚洲日本va午夜在线影院| 性欧美长视频| 亚欧美中日韩视频| 亚洲大胆美女视频| 午夜精品视频在线观看一区二区| 久久先锋影音| 国产日韩精品一区二区三区在线| 日韩手机在线导航| 欧美1区2区视频| 久久成人精品| 国产精品一区二区三区四区五区| 亚洲人成在线免费观看| 久久婷婷蜜乳一本欲蜜臀| 亚洲无人区一区| 欧美日韩国产成人在线91| 亚洲福利视频免费观看| 久久久久国产精品一区二区| 亚洲一区二区视频在线| 欧美日韩久久| 一区二区三区波多野结衣在线观看| 欧美不卡在线| 噜噜噜91成人网| 在线看片成人| 另类春色校园亚洲| 久久三级福利| 亚洲欧洲日韩女同| 亚洲国产高清aⅴ视频| 久久中文字幕导航| 一色屋精品视频免费看| 久久久夜精品| 久久久av毛片精品| 欧美一区二区| 欧美顶级少妇做爰| 亚洲在线观看免费| 国产精品久久久久久av下载红粉| 99亚洲伊人久久精品影院红桃| 欧美激情精品久久久六区热门| 久久久久久久精| 亚洲国产视频一区| 亚洲精品婷婷| 欧美午夜片在线观看| 9久草视频在线视频精品| 亚洲麻豆一区| 国产精品天天摸av网| 欧美一级夜夜爽| 欧美一区二区日韩一区二区| 国产一区二区三区无遮挡| 美女精品一区| 欧美日韩高清免费| 欧美一区二区三区视频| 久久九九热re6这里有精品| 最新成人av网站| 一区二区久久| 国户精品久久久久久久久久久不卡| 噜噜噜在线观看免费视频日韩| 欧美1区免费| 亚洲精品视频在线| 亚洲少妇中出一区| 影音先锋另类| 99精品欧美一区二区三区综合在线| 国产精品亚洲一区二区三区在线| 久久午夜电影| 欧美日韩亚洲一区二| 久久九九精品99国产精品| 韩国成人福利片在线播放| 亚洲国产精品99久久久久久久久| 欧美午夜不卡视频| 另类av导航| 欧美午夜精品久久久久免费视| 久久免费视频在线观看| 久久综合影音| 欧美在线观看视频在线 | 一区二区三区色| 红桃视频一区| 亚洲视频狠狠| 亚洲精品美女免费| 欧美尤物巨大精品爽| 亚洲美女网站| 欧美在线亚洲在线| 中文欧美在线视频| 快播亚洲色图| 久久久之久亚州精品露出| 欧美日韩伊人| 亚洲成人资源网| 国内精品**久久毛片app| 亚洲美女av网站| 亚洲精品欧美精品| 久久久精品免费视频| 午夜伦欧美伦电影理论片| 欧美大片第1页| 欧美成人午夜视频| 国产一区亚洲| 亚洲大胆av| 久久国产手机看片| 一本到高清视频免费精品| 久久av资源网| 亚洲一二区在线| 校园激情久久| 欧美一级视频| 国产精品羞羞答答| 亚洲一二区在线| 亚洲在线一区| 国产精品久久久久久妇女6080| 亚洲人成在线免费观看| 亚洲精品久久久久久久久久久| 久久久久久97三级| 老牛嫩草一区二区三区日本| 国产综合第一页| 欧美在线观看视频在线| 久久国产精品99国产| 国产视频自拍一区| 久久er99精品| 欧美国产综合视频| 亚洲精品一区二区在线| 欧美黄在线观看| 亚洲人成网站精品片在线观看 | 亚洲一区二区三区乱码aⅴ| 亚洲午夜久久久久久久久电影院| 欧美日韩国产电影| 亚洲美女色禁图| 欧美一级久久久| 国产日韩欧美一二三区| 欧美一区二区国产| 免费在线看一区| 日韩视频免费观看高清完整版| 欧美激情在线有限公司| 中日韩午夜理伦电影免费| 欧美伊人久久大香线蕉综合69| 欧美成人一区在线| 亚洲一区二区精品视频| 久久国产福利| 亚洲欧洲免费视频| 欧美激情aaaa| 欧美 日韩 国产在线 | 欧美一区二区视频免费观看| 亚洲嫩草精品久久| 久久精品一区二区三区不卡牛牛| 欧美呦呦网站| 亚洲韩国精品一区| 亚洲欧美日韩精品一区二区| 欧美成人精品在线视频| 国产精品蜜臀在线观看| 午夜影视日本亚洲欧洲精品| 亚洲免费高清视频| 欧美激情国产日韩| 亚洲永久免费av| 亚洲国产aⅴ天堂久久| 亚洲欧美综合网| 亚洲国产另类久久精品| 亚洲先锋成人| 日韩亚洲精品在线| 欧美日韩一区高清| 久久精品视频网| 一区二区三区视频观看| 久久综合福利| 亚洲综合另类| 日韩视频免费观看高清在线视频| 欧美电影免费观看高清| 性欧美1819sex性高清| 亚洲欧洲一区二区在线播放| 欧美freesex8一10精品| 欧美专区福利在线| 一区二区三区高清| 亚洲国产成人tv| 国产一区二区剧情av在线| 欧美日韩亚洲一区二区三区四区 | 亚洲欧美国产毛片在线|