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

APUE學習筆記第10章:線程控制——屬性、互斥量、重入、線程特定數據與取消機制

     編輯:關於C語言

APUE深度讀書筆記:第10章 線程控制

《Unix環境高級編程》(APUE)第10章系統講解了POSIX線程的進階控制機制。本文提煉核心知識點,結合代碼示例與內核視角,幫助開發者構建完整的線程控制知識體系。

章節脈絡回顧

前九章已涵蓋:標準IO、文件IO、文件目錄、系統數據、進程環境/控制/關係、信號、線程基礎。本章聚焦線程屬性自定義與同步機制深度。

1. 線程屬性:定制線程行為

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頁大小棧溢出檢測

2. 互斥量屬性與類型:不只是鎖那麼簡單

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);

3. 重入與線程安全:從errno看演進

  • 線程安全:同一時間多線程調用不會損壞數據
  • 可重入:可被中斷後重新進入(信號處理程序要求)
  • 可重入 ⇒ 線程安全,逆不成立

POSIX規範:庫函數必須線程安全,非線程安全版本加_unlocked後綴;可重入版本加_r後綴(如getpwnam_r)。

errno的線程化演進

/* 早期:全局變量 */
extern int errno;

/* 現代:線程局部存儲(TLS)宏 */
#define errno (*__errno_location())  // 每個線程獨有的errno地址

/* 編譯器展開後 */
(*__errno_location())  // 調用函數獲取當前線程errno地址

4. 線程特定數據 (TSD):線程私有全局變量

鍵值對機制:

/* 創建鍵,可選析構函數 */
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);

典型應用:數據庫連接池每線程一連接、線程專用緩衝區、錯誤上下文保存。

5. 線程取消機制:優雅退出的藝術

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規定的**必有取消點**(阻塞系統調用):

  • I/O:read、write、open、close、select、poll
  • 同步:pthread_cond_wait、pthread_join、sem_wait
  • 其他:sleep、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();  // 人工取消點
        /* 計算邏輯 */
    }
}

6. 線程與信號:複雜的交互模型

  • 每線程獨有sigmask(信號屏蔽集)
  • 進程共享sigpending(未決信號集)
  • 信號發生時:內核選擇一個**未屏蔽該信號**的線程投遞
  • pthread_kill(tid, sig):向指定線程發信號
  • pthread_sigmask:修改當前線程信號屏蔽集

7. fork與線程:經典陷阱

  • fork僅複製調用線程,其他線程消失
  • 子進程繼承父進程鎖狀態 → 若父進程其他線程持有鎖,子進程死鎖
  • 解決方案:pthread_atfork註冊prepare/parent/child處理程序,在fork前解鎖、fork後重新初始化

8. OpenMP簡介:指令式並行編程

#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互補。

核心總結

  1. 線程屬性讓創建更可控,棧大小調優是大量線程場景關鍵
  2. 互斥量類型選擇直接影響調試效率與運行性能,開發期用ERRORCHECK
  3. 線程取消機制的核心是「推遲到取消點」+「清理處理程序棧」保證資源安全
  4. 線程與信號、fork的交互極易產生難以復現的Bug,需建立團隊規範
  5. TSD是實現線程隔離上下文的標準模式

掌握線程控制不僅是API調用,更是對併發模型、內核調度、資源生命週期的深度理解。APUE這一章是從「會用線程」進階到「駕馭併發」的必經之路。

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