《Unix環境高級編程》(APUE)第10章系統講解了POSIX線程的進階控制機制。本文提煉核心知識點,結合代碼示例與內核視角,幫助開發者構建完整的線程控制知識體系。
前九章已涵蓋:標準IO、文件IO、文件目錄、系統數據、進程環境/控制/關係、信號、線程基礎。本章聚焦線程屬性自定義與同步機制深度。
pthread_attr_t控制線程創建時的特性:
pthread_attr_t attr;
pthread_attr_init(&attr);
/* 關鍵屬性設置 */
pthread_attr_setstacksize(&attr, 1024*1024); // 設置棧大小 1MB
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 分離態
pthread_attr_setguardsize(&attr, 4096); // 保護區大小
/* 測試:減小棧大小能創建更多線程 */
int max_threads = 0;
while (pthread_create(&tid, &attr, thread_func, NULL) == 0) max_threads++;
printf("Max threads with 1MB stack: %d\n", max_threads);
pthread_attr_destroy(&attr);
| 屬性 | 設置函數 | 默認值 | 典型用途 |
|---|---|---|---|
| 棧大小 | setstacksize | 系統默認(通常8MB) | 大量線程場景減小棧;深度遞歸增大棧 |
| 分離狀態 | setdetachstate | 可連接 | 無需join的獨立任務 |
| 調度策略/優先級 | setschedpolicy/param | 繼承父進程 | 實時任務需顯式設置SCHED_FIFO/RR |
| 保護區 | setguardsize | 頁大小 | 棧溢出檢測 |
pthread_mutexattr_t支持進程共享屬性:
pthread_mutexattr_t mattr; pthread_mutexattr_init(&mattr); pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED); // 跨進程共享 pthread_mutex_init(&mutex, &mattr);
| 類型 | 重複加鎖 | 解鎖他人鎖 | 重複解鎖 | 適用場景 |
|---|---|---|---|---|
| NORMAL (默認) | 死鎖 | 未定義 | 未定義 | 高性能,調試困難 |
| ERRORCHECK | 返回EDEADLK | 返回EPERM | 返回EPERM | 調試階段,捕獲邏輯錯誤 |
| RECURSIVE | 允許(計數器) | 返回EPERM | 返回EPERM | 遞歸函數/複雜調用鏈 |
| DEFAULT | 實現定義 | 實現定義 | 實現定義 | 通常映射NORMAL |
設置類型:
pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_ERRORCHECK);
POSIX規範:庫函數必須線程安全,非線程安全版本加_unlocked後綴;可重入版本加_r後綴(如getpwnam_r)。
/* 早期:全局變量 */ extern int errno; /* 現代:線程局部存儲(TLS)宏 */ #define errno (*__errno_location()) // 每個線程獨有的errno地址 /* 編譯器展開後 */ (*__errno_location()) // 調用函數獲取當前線程errno地址
鍵值對機制:
/* 創建鍵,可選析構函數 */ pthread_key_t key; pthread_key_create(&key, destructor_func); // 線程退出時自動調用 /* 設置/獲取線程私有值 */ pthread_setspecific(key, thread_specific_data); void *data = pthread_getspecific(key); /* 釋放鍵 */ pthread_key_delete(key);
典型應用:數據庫連接池每線程一連接、線程專用緩衝區、錯誤上下文保存。
pthread_cancel只是發送請求,目標線程自行決定響應方式:
/* 狀態:啟用/禁用取消 */ pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &oldstate); // 默認啟用 pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &oldstate); // 臨界區禁用 /* 類型:異步/推遲(默認) */ pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &oldtype); // 默認:在取消點響應 pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, &oldtype); // 立即終止(危險)
POSIX規定的**必有取消點**(阻塞系統調用):
read、write、open、close、select、pollpthread_cond_wait、pthread_join、sem_waitsleep、pause、wait、waitpid**可選取消點**:printf、malloc、gethostbyname等,視實現而定。
void *thread_func(void *arg) {
void *p = malloc(1024); // 分配資源
pthread_cleanup_push(free, p); // 註冊清理處理程序(非取消點)
int fd = open("file", O_RDONLY); // 取消點!若這裡被取消
pthread_cleanup_push(close, &fd); // 不會執行
/* ... 業務邏輯 ... */
pthread_cleanup_pop(1); // 正常退出時執行清理(1=執行)
pthread_cleanup_pop(1);
return NULL;
}
若在malloc後立即取消,無清理處理程序會洩漏內存。推遲到open(取消點)響應,清理棧按序執行free→close,完美釋放資源。
/* 純計算長任務需定期檢查 */
void compute_heavy() {
for (int i = 0; i < 1e9; i++) {
if (i % 1000000 == 0) pthread_testcancel(); // 人工取消點
/* 計算邏輯 */
}
}
sigmask(信號屏蔽集)sigpending(未決信號集)pthread_kill(tid, sig):向指定線程發信號pthread_sigmask:修改當前線程信號屏蔽集fork僅複製調用線程,其他線程消失pthread_atfork註冊prepare/parent/child處理程序,在fork前解鎖、fork後重新初始化#include <omp.h>
int main() {
#pragma omp parallel sections
{
#pragma omp section
printf("[%d]: Hello\n", omp_get_thread_num());
#pragma omp section
printf("[%d]: World\n", omp_get_thread_num());
}
return 0;
}
/* 編譯: gcc -fopenmp -o demo demo.c */
OpenMP通過編譯器指令實現並行,適合數值計算循環並行化,與pthread互補。
掌握線程控制不僅是API調用,更是對併發模型、內核調度、資源生命週期的深度理解。APUE這一章是從「會用線程」進階到「駕馭併發」的必經之路。