在多線程網絡編程中,常遇到這樣的場景:一個線程阻塞在 recv 調用上等待數據,另一個線程嘗試發送信號(如 SIGUSR1)來喚醒該阻塞線程,但發現信號處理函數執行完畢後,recv 依然保持阻塞狀態,並未返回錯誤或被中斷。
默認情況下,系統調用(如 recv)在被信號中斷後會自動重啟(SA_RESTART 行為),導致信號無法有效打破阻塞。此外,若無超時機制,線程可能永久等待。
SO_RCVTIMEO通過 setsockopt 為 Socket 設置接收超時,使 recv 定期返回超時錯誤(EAGAIN 或 EWOULDBLOCK),從而獲得檢查退出條件的機會。
struct timeval tv; tv.tv_sec = 10 * 60; // 10 分鐘,不可為 0,也不可過長 tv.tv_usec = 0; setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
注意:超時值的上限取決於系統實測。作者在 Fedora 18 (64-bit) 上可設置長達 1 個月,但在 LFS (x86 32-bit) 上長超時會導致設置失敗;10 分鐘等較短超時在 LFS 上驗證可行,但未測試邊界值。
sigaction 並禁用 SA_RESTART顯式設置信號處理行為,確保系統調用被信號中斷後返回 EINTR,而非自動重啟。
static Sigfunc* _signal(int signum, Sigfunc* func) {
struct sigaction act, oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0; // 關鍵:不設置 SA_RESTART
#if 0
if (signum != SIGALRM)
act.sa_flags |= SA_RESTART; // 故意註釋掉
#endif
if (sigaction(signum, &act, &oact) < 0)
return (SIG_ERR);
return oact.sa_handler;
}
結合超時機制與正確的信號語義,即可可靠地實現阻塞線程的喚醒與優雅退出。