準(zhǔn)則4:請不要做線程的異步撤消的設(shè)計
Posted on 2008-12-19 11:01 lymons 閱讀(1291) 評論(0) 編輯 收藏 引用 所屬分類: C++ 、C 、Unix/Linux 、文章翻譯![]() |
From 2008精選 |
原文地址:http://d.hatena.ne.jp/yupo5656/20040724/p1
■[C++] UNIX上的C++程序設(shè)計守則 (4)

鐵則4: 請不要做線程的異步撤消的設(shè)計
- 線程的異步撤銷是指: 某個線程的執(zhí)行立刻被其他線程給強制終止了
- 請不要單單為了讓“設(shè)計更簡單”或者“看起了更簡單”而使用線程的異步撤消
咋一看還是挺簡單的。但是搞不好可能會引起各種各樣的問題。請不要在不能把握問題的實質(zhì)就做出使用線程的異步撤消的設(shè)計!
在pthread的規(guī)格說明中,允許一個線程可以強制中斷某個線程的執(zhí)行。這就是所說的異步撤消。
線程的撤消有下面的兩種方式。
- 方式1: 異步撤消(PTHREAD_CANCEL_ASYNCHRONOUS)
- 撤銷動作是馬上進行的
- 方式2: 延遲撤銷(PTHREAD_CANCEL_DEFERRED)
- 撤消動作,是讓線程的處理一直被延遲到撤消點才會去執(zhí)行
還有,到底是用哪種撤消方式,不是撤消側(cè),而是被撤銷側(cè)能夠決定的*1。另外,在被撤銷側(cè)也能夠選擇完全禁止撤消的這種方式 *2。
會造成什么問題呢
那么,讓我看看亂用線程的異步撤消會引起什么問題呢。看過準(zhǔn)則3的人可能會知道,在下面的腳本里,被撤銷線程以外的任意一個線程會被死鎖。
- 線程1中調(diào)用malloc函數(shù)正在做內(nèi)存分配的過程中,線程2異步撤消了線程1的處理
- 線程1馬上被撤銷,但是malloc函數(shù)中的互斥鎖就沒有線程去解除了
- 后面的任意一個線程如果再次調(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個。
- pthread_cancel
- pthread_setcancelstate
- pthread_setcanceltype
而且、里面還有"No other functions are required to be async-cancel-safe"這樣的記載。因此,Linux的場合,如果在文檔里沒有記載成async-cancel safety的函數(shù),我們還是把它假定成不安全的函數(shù)為好!
如何避免這些問題呢
在多線程編程中為了安全的使用異步撤消處理、有沒有回避死鎖的方法呢?我們試著想了幾個。他們與準(zhǔn)則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ù)。這和在準(zhǔn)則3中介紹的pthread_atfork函數(shù)有點兒類似。用這個函數(shù)登錄的回調(diào)函數(shù)來清除線程的數(shù)據(jù)和鎖,就可以回避死鎖了。
...但是,pthread_cleanup_push函數(shù)登錄的回調(diào)函數(shù),在「延遲撤消」的場合是不能被調(diào)用的。因此、這個回避方法對于異步撤消沒有什么大的作用。
回避方法4: 不要執(zhí)行異步撤消處理
最后是、不要執(zhí)行異步撤消處理。反而代之的是、
- 設(shè)計成不依賴使用異步撤消那樣的處理
或者
- 不得不使用線程撤消的話、不做異步撤消而作延遲撤消的處理
這是比較實際的做法,是我們值得推薦的。