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

可重入函數深度解析:多任務環境下的線程安全編程指南

     編輯:關於C語言

在多任務/中斷環境中,可重入性 是函數能否被安全並發調用的核心屬性。本文從定義、反例、修復方案三個維度建立完整認知體系。

什麼是可重入函數?

可重入函數 可被多個任務/中斷同時調用,不導致數據損壞。特徵:
  • 僅使用局部變量(棧/寄存器,天然隔離)
  • 或使用全局變量但有完備保護機制(互斥鎖、關中斷、原子操作)
  • 不調用非可重入函數
  • 不操作共享硬體資源(或已互斥)

經典可重入案例:strcpy

void strcpy(char *dest, char *src) {
    while (*dest++ = *src++) { ; }
    *dest = '\0';
}

參數通過棧傳遞,每次調用擁有獨立副本,循環僅操作參數指向的內存 → 天然可重入。

經典非可重入反例:swap 全局暫存變量

int Temp;  // 全局變量!
void swap(int *x, int *y) {
    Temp = *x;
    *x = *y;
    *y = Temp;
}

並發災難時序

  1. Task_L(低優先級)調用 swap(&a, &b),執行 Temp = *x(Temp=1)
  2. 中斷發生,Task_H(高優先級)就緒搶佔
  3. Task_H 調用 swap(&c, &d),Temp = *x(Temp=3),完成交換
  4. Task_H 延遲釋放 CPU
  5. Task_L 恢復,執行 *y = Temp → 用 Task_H 的 Temp=3 覆蓋 b!

非可重入並發錯誤示意

後果: Task_L 數據損壞,且極難復現(需特定中斷時機)。

三種修復方案對比

方案代碼修改優點缺點/適用場景
局部變量化(首選)void swap(int *x, int *y) { int Temp = *x; ... }零開銷、天然線程安全、無副作用遞歸/大對象棧溢出風險
關中斷保護void swap(...) { INT_OFF; ... INT_ON; }不改函數簽名、兼容舊代碼增加中斷延遲、禁用於長操作、多核無效
信號量/互斥鎖SemPend(mutex); ... SemPost(mutex);任務級互斥、支援多核、可超時上下文切換開銷、死鎖風險、不可在 ISR 用

進階:C/C++ 標準庫可重入性

  • C11/C++11 起:標準庫函數若無明確說明為非可重入,默認是線程安全的(內部用 TLS 或鎖)
  • 經典非可重入:strtok、asctime、localtime、rand、getenv、tmpnam
  • 可重入替代版:strtok_r、localtime_r、rand_r、getenv_r(POSIX)
  • malloc/free:現代實現均為線程安全(arena/per-thread cache)

工程檢查清單

✅ 判斷函數是否可重入:

  1. 是否使用 static / 全局變量?
  2. 是否調用非可重入庫函數?
  3. 是否操作共享硬體(UART、Flash、GPIO)?
  4. 是否有 malloc/free 且分配器非線程安全?
  5. 是否修改傳入指針指向的共享數據結構?

若以上均為「否」 → 可重入。否則需加鎖/改局部/用重入版。

總結

可重入性是嵌入式/系統級開發的「免疫系統」。優先將狀態局部化(棧上),實在需要共享狀態時,用最小粒度鎖保護。代碼評審時,對每個非靜態函數問一句:「這能被中斷/高優任務打斷重入嗎?」能攔截 90% 的並發 Bug。

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