首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

多線程環境下 Socket 阻塞與信號喚醒失敗的解決方案

     編輯:關於C語言

問題背景

在多線程網絡編程中,常遇到這樣的場景:一個線程阻塞在 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;
}

結合超時機制與正確的信號語義,即可可靠地實現阻塞線程的喚醒與優雅退出。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved