轉載地址:http://blog.sina.com.cn/s/blog_4a72b0f60100r7ad.html
多線程中,線程需要暫停原因多種多樣,基本分為兩大類,1是等待資料,2是系統資源緊張,停止計算以把資源讓給其他線程.一般初學者會使用的方法就是使用SuspendThread和ResumeThread這兩個API或者他們的封裝函數進行操作,許多教材上都是這麼說的,但這麼作會帶來及其嚴重的問題。
這兩個API的問題是無法確定函數被暫停位置,這是一個足以讓任何多線程程式崩潰的問題。想象一種情況,A線程式控制制B線程暫停和繼續,AB共用一個變數C,C由互斥量D加鎖使得其可以在AB間安全共用。現在有一種最糟糕的情況,A暫停B的時候,B已經Lock了D,但是還沒有Unlock.而A只有在Lock了D以後才能繼續B,於是兩個線程陷入死結。並且幾乎無法穩定重現這個bug,在多數情況下,也許1000次只有一次會死結,於是你的程式變成定時炸彈。第二種情況更加常見,比如執行SQL查詢的代碼,獲得了一個recordset的COM對象,於是通過while迴圈提取資料,在迴圈的過程中,線程被暫停。過了一段時間軸程繼續執行,你立刻會收到一個異常:管道的另一端沒有任何進程。相當的莫名奇妙,其實用COM執行SQL的時候,返回結果並沒有移動到使用者進程的記憶體空間,而是SQL伺服器保留了一個進程儲存資料,但COM組件要求資料的時候再傳送過去,太長時間沒有資料提取要求,SQL伺服器認為你的程式崩潰了,於是開始回收資源,關閉進程,這時你的程式倒是繼續執行了。這個異常即使捕獲也幾乎無法處理,要復原程式狀態,重新執行查詢麻煩到極點,你只有一條路,就是提醒使用者程式出錯了,我想使用者的體驗一定相當糟糕。
暫停線程必須想一個辦法在能控制線程在能暫停地方暫停,暫停點不能有任何資料已經上鎖,上面說的那種while迴圈中也不能被暫停,還有一種就是當有大量記憶體被申請且還沒有釋放之前最好也不要被暫停。我建議大家用手動模式的訊號量來實現。像這樣申請一個訊號量:
hEvent=::CreateEvent(NULL,TURE,TURE,lpName);//第二個參數是TURE,設定為手動模式,防止::WaitForSingleObject改變訊號量的狀態。
在所有線程可以被暫停地方加上:
::WaitForSingleObject(hEvent,-1);
這樣,只需要用這兩個函數
::SetEvent(hEvent);//繼續
::ResetEvent(hEvent);//暫停
發出暫停和繼續的命令就可以了。線程只會停在你認為能停的地方,可以避免上述兩種類型的BUG,不只是C++,所有語言都可以使用這個思想。
再增加一個,關於sleeping的替代,為什麼不用sleeping?因為設定下sleeping以後不到時間軸程是無法繼續的,如果臨時要立刻結束線程怎麼辦?殺掉線程?不對,如果線程需要清理資源呢?結果是記憶體泄露,而且如果線程有COM智能指標一類的東西也不能正常工作,這也可以嚴重到導致程式崩潰,或者導致其他程式崩潰。你可以用類似上面的方法,只不過訊號量的作用改成標記是否線程結束,平時用sleeping的時候改成等待訊號量到逾時就可以了,如果不幸沒逾時,那就是收到線程必須馬上結束的訊號了,該幹什麼幹什麼,然後自然退出。