前言
Linux 系統程式設計中,sleep 族函數控制執行緒休眠,fflush 控制輸出緩衝區刷新,而標準 I/O 的三種緩衝模式(全緩衝、行緩衝、無緩衝)直接決定程式輸出行為。本文結合 POSIX 標準、glibc 實現與 APUE 經典理論,系統梳理這三大主題的坑點與最佳實踐。
一、sleep 族函數對比
| 函數 | 標準 | 精度 | 參數單位 | 備註 |
sleep(sec) | POSIX.1 | 秒 | unsigned int 秒 | 信號中斷返回剩餘秒數 |
usleep(usec) | BSD/POSIX.1-2001 (已废弃) | 微秒 | useconds_t | 最大 1,000,000,建議改用 nanosleep |
nanosleep(&ts, &rem) | POSIX.1b | 奈秒 | struct timespec | 高精度、支援剩餘時間返回、信號安全 |
clock_nanosleep() | POSIX.1b | 奈秒 | clockid_t + timespec | 可指定時鐘源(CLOCK_MONOTONIC 避免系統時間調整影響) |
select/pselect | POSIX.1 | 微秒/奈秒 | timeval/timespec | 可同時監控 fd,常用於精確定時器 |
跨平台差異陷阱
- Windows
Sleep(ms):毫秒、windows.h、大寫 S。
- Linux
sleep(s):秒、unistd.h、小寫 s。
- 移植建議:封裝
msleep(msec) 內部呼叫 nanosleep。
範例:可被信號中斷的精確休眠
#include
#include
void msleep(long msec) {
struct timespec ts = { msec / 1000, (msec % 1000) * 1000000 };
while (nanosleep(&ts, &ts) == -1 && errno == EINTR)
continue; // 信號中斷則繼續休眠剩餘時間
}
二、fflush 深度解析
C99 標準定義
int fflush(FILE *stream);
- 輸出流/更新流且最近非輸入操作:將用戶空間緩衝區數據寫入內核(文件/終端/管道)。
- 輸入流(stdin):行為未定義(C99 §7.19.5.2)。嚴禁
fflush(stdin)。
- stream = NULL:刷新所有輸出流。
- 返回值:成功 0,失敗 EOF(設定
errno)。
常見誤區
fflush(stdin) 清除輸入緩衝:未定義行為,MSVC 擴展實現但不可移植。正確做法:int c; while ((c = getchar()) != '\n' && c != EOF);
- 認為
printf 立即顯示:標準輸出連終端預設行緩衝,遇 \n 才刷新;重導向檔案變全緩衝(4KB/8KB),需手動 fflush(stdout) 或 setvbuf(stdout, NULL, _IONBF, 0)。
fclose 隱含 fflush:正常關閉無需額外呼叫,但 abort()/_exit() 會跳過,數據可能遺失。
三、標準 I/O 三種緩衝模式(APUE Ch.5 核心)
| 模式 | 觸發刷新條件 | 典型場景 | 預設檔案 |
| 全緩衝 | 緩衝區滿(通常 4KB/8KB)、fflush、fclose、程式正常結束 | 磁碟檔案、管道、Socket(非終端) | 檔案、重導向的 stdout/stderr |
| 行緩衝 | 遇換行符 \n、緩衝區滿、fflush、讀取無緩衝/行緩衝流輸入 | 連接終端的 stdout/stderr | 互動式終端的 stdout/stderr |
| 無緩衝 | 每次 I/O 直接系統調用(write/read) | stderr、setvbuf(..., _IONBF, 0) | stderr(標準錯誤) |
ANSI C / glibc 預設規則
stdin、stdout:若 isatty(fileno(fp)) 真 → 行緩衝,否 → 全緩衝。
stderr:永遠無緩衝(確保錯誤訊息即時顯示)。
- 可用
setvbuf(fp, buf, mode, size) 強制切換(必須在首次 I/O 前呼叫)。
緩衝區互動陷阱:讀取行緩衝輸入會刷新所有行緩衝輸出
printf("Prompt: "); // 無 \n,留在行緩衝區
fflush(stdout); // 必須手動刷新,否則使用者看不到提示
fgets(buf, sizeof(buf), stdin); // 讀取 stdin(行緩衝)會自動刷新 stdout
四、實戰最佳實踐清單
- 互動式提示:
printf("> "); fflush(stdout); 或 setvbuf(stdout, NULL, _IONBF, 0);(調試階段)。
- 日誌寫入:
setvbuf(logfp, NULL, _IOLBF, 0); 行緩衝,確保每行即時落盤;關鍵審計日誌用 _IONBF。
- 高效能批次寫入:大塊
malloc 緩衝區 + setvbuf(fp, buf, _IOFBF, BUFSIZ*16); 減少系統調用。
- 多進程共享檔案:
open(..., O_APPEND) + 無緩衝 write,避免用戶空間緩衝區導致資料交錯。
- 信號處理函數中:僅呼叫
async-signal-safe 函數(write 可、fprintf/fflush 不可)。
五、總結
sleep 秒級、nanosleep 奈秒級、clock_nanosleep 可選時鐘源,避免 usleep 舊介面。
fflush 僅用於輸出流/更新流,嚴禁 fflush(stdin)。
- 理解「終端→行緩衝、檔案→全緩衝、stderr→無緩衝」預設規則,善用
setvbuf 掌控刷新時機。
- APUE 第 5 章是系統程式設計師的必讀聖經,建議精讀並實作驗證。