在上次的帖子聊了C++多線程的跨平臺問題,後來感受意猶未盡。今天順便說一下開發C++多線程應用程序時,有關調試和測試的一些注意事項。下面這些注意事項主要是針對C++,不過有些對於其它的語言也適用。ios
1、關於設置斷點和單步執行程序員
不少同窗很是依賴於調試器的斷點功能和單步功能。這在單線程狀況下倒還好(不過有些單線程但涉及GUI的程序,也會有點麻煩)。至於多線程程序的調試,這兩種手段簡直就是噩夢的開始。多線程形成的主要問題大都和競態條件(Race Condition,詳細解釋看「這裏 」)有關。編程
而設置斷點或單步跟蹤可能會嚴重干擾 多線程之間的競爭狀態。致使你看到的是一個假象。好比原本有兩個線程併發執行,存在某些不和諧的Bug(由競態引發)。一旦你在某一個線程設置了斷點,該線程在斷點處停住了,只剩下另外一個線程在跑。這時候,併發的場景已經徹底被破壞了,你經過調試器看到的可能 是一個和諧的場景。安全
稍微跑一下題。這很相似量子力學的「測不許原理」,觀測者的觀測行爲干擾了被測量的客體,致使觀測者看到的是一個干擾後的現象。多線程
2、關於Log輸出併發
既然斷點和單步很差用。那咋辦捏?一個替代方案是輸出log日誌。它能夠有效減輕斷點和單步所致使的(針對競態條件的)反作用。函數
一、傳統Log機制的問題測試
傳統的log輸出主要是打印到屏幕或者輸出到文件。對於C++而言,標準庫內置的類和函數(好比cout、printf、fputs)可能會有線程安全的問題(和編譯器的具體實現有關)。尤爲是標準流類庫(iostream)的八個全局對象,更是要當心慎用。輕則輸出的log文本混雜,重則致使程序崩潰。線程
鑑於上述緣由,應該儘可能使用第三方線程庫內置的log機制來搞定log輸出功能。好比ACE內置的ACE_Log_Msg等。調試
二、Log函數要短小精悍
不少狀況下,咱們會包裝一個公用的函數來實現log輸出功能。而後在該函數內部調用線程庫的log類/函數。爲了避免影響線程的競態條件,這個log函數要儘量簡單輕便:不要涉及太多雜七雜八的雜事、千萬別進行耗時的操做、儘可能不操做一些全局的變量。
三、Log的反作用
不過捏,即便log函數再短小精悍,也仍是有可能影響競態條件(畢竟log也有開銷,也要消耗CPU時間)。
萬一競態條件受到log的影響,那就比較棘手了。我之前就碰到過這種狀況:加了log,程序沒有問題;去掉log,程序隨機崩潰。這種狀況通常有兩種可能:要麼是log功能自己有問題,要麼是程序的競態條件很是敏感(連log的開銷都會有影響)。
這時候你能依靠的就只有肉眼和人腦了。先把相關的代碼和文檔仔細看上幾遍(最好再找其餘有經驗的人一塊兒Code Review),而後你們一塊兒開動腦筋使勁琢磨。
3、關於Debug版本和Release版本
C++程序常常有Debug版本和Release版本的區別。有些時候,這也會致使一些多線程的問題。
因爲Debug版本包含了一些調試信息、啓用了某些調試機制(好比assert宏)。因此就可能 影響到多線程的競爭狀態。在倒黴的時候,會碰上Debug版本工做正常,Release版本程序隨機崩潰。要避免這種狀況,能夠考慮下面兩個辦法:
一、放棄使用Debug版本
你能夠乾脆放棄使用Debug版本。在這種狀況下,你須要考慮把諸如assert之類調試相關的宏替換成本身的一套宏,使得在非Debug版本下也能夠生效。
二、兩種版本同步測試
使用此方法,程序員平時自測可使用Debug版本,可是測試人員平常測試的必須是Release版本。具體的操做步驟能夠利用每日構建來輔助進行(每日構建的介紹參見「這裏 」)。必定要避免:在平時僅僅搞Debug版本的測試,等到發佈前夕再製做Release版本。這種作法是很是危險的!