因為某人說我的文章,看第一眼就不想看了,因為用膝蓋想也可以知道,太花腦筋。所以我還是改變一下風格,多寫點簡短的好了,剛好可以把一些零散的程式設計經驗,記錄下來,以後有空再集結整理。

在 thread function 裡用 catch (...) 攔截所有可能的例外

這是因為,thread function 的啟動,是透過系統完成,無法傳遞 exception,更因為 thread function 相當於 main() of thread,是 stack unwinding 的終點,如果不用 catch (...) 把「所有」可能的例外攔截下來,則視系統不同,輕則整個 process 結束,重則僅該 thread 無聲無息地消失,程式裡其他 thread 繼續執行,整個程式看起來沒什麼問題,但實際上卻有一個 thread 已經不見了,等到要發現問題,可能要在一個月以後。

最簡單的 thread function,應該這麼寫:

int thread_func(void* param)
{
    // nothing above try
    try {
        // do real job
    }
    catch (...) {
        // ellipsis to catch all possible exceptions
    }
    // nothing after catch
    return 0;
}

在 thread proxy 裡用 catch (...) 攔截所有可能的例外,並提供預設處理機制

同上面的理由,如果 thread library 是我們自己撰寫的,則通常會有個 thread proxy,library 把 thread proxy 傳給 OS,OS 啟動新的 thread 時,先執行 thread proxy,然後 thread proxy 才轉呼叫真正的 thread function。因為不同平台的 native thread function 的 prototype、calling convention 可能都不一樣,因此依據平台的不同,必須準備不同版本的 thread proxy,這樣才可以讓 thread function 的 prototype、calling convention 一致化。

因此,對於 OS 來說,這個 thread proxy 才是真正的 thread function,thread proxy 是第一線,原來的 thread function 是第二線。因為寫 library 是要給人用,有可能用的人沒有注意到前一點,忘記在 thread function 裡 catch (...),還好我們還有更底層的 thread proxy,可以在此 catch (...) 並做一些處理,就可以避免意外發生。

例如 Boost.Thread 1.37.0 的 thread proxy 是這麼寫的:

void* thread_proxy(void* param)
{
    boost::detail::thread_data_ptr thread_info
        = static_cast<boost::detail::thread_data_base*>(param)->self;
    thread_info->self.reset();
    detail::set_current_thread_data(thread_info.get());
    try {
        thread_info->run();
    }
    catch(thread_interrupted const&) {
    }
    catch(...) {
        std::terminate();
    }

    detail::tls_destructor(thread_info.get());
    detail::set_current_thread_data(0);
    boost::lock_guard<boost::mutex> lock(thread_info->data_mutex);
    thread_info->done=true;
    thread_info->done_condition.notify_all();
    return 0;
}

Boost 選擇的處理方式,是呼叫 std::terminate(),把程式結束,這樣至少 thread 不會無聲無息地消失。