一、基本概念
同步與互斥是多任務/多線程編程中的兩個核心機制:
同步:指多個任務之間存在明確的先后順序,一個任務必須等待另一個任務完成某些操作后才能繼續執行。
互斥:指多個任務在同一時刻爭搶使用同一資源(臨界資源),必須通過某種機制保證同一時間只有一個任務可以使用該資源。
二、線程安全問題的由來
問題描述
當多個任務同時訪問一個共享資源(如全局變量)時,如果沒有適當的保護機制,就會出現數據不一致的問題。
示例分析:全局變量?count++
c
int count = 0; // 兩個線程同時執行 count++
count++
?實際上包含三個步驟(非原子操作):
讀取?
count
?的值到寄存器對寄存器中的值加 1
將結果寫回?
count
?的內存地址
執行流程(可能出現的問題):
線程 A 讀取?
count = 0
,準備加 1;此時切換到線程 B,也讀取?
count = 0
,完成加 1 并寫回,count
?變為 1;切換回線程 A,繼續執行加 1(基于之前讀到的 0),寫回后?
count
?仍為 1。
預期結果為 2,實際結果為 1,這就是典型的線程安全問題,也稱為數據競爭。
三、同步機制的缺陷
使用同步機制(如忙等待)會導致任務死等,浪費 CPU 資源。
應避免使用?
while()
?循環進行無意義的等待,而應使用阻塞機制讓出 CPU。
四、全局變量在 RTOS 中的存儲位置
問題:全局變量存儲在哪個棧中?
答案:全局變量不屬于任何一個任務的棧。
內存區域劃分:
代碼區 (Text Segment):存放程序指令。
全局/靜態數據區 (Data/BSS Segment):
存放全局變量和靜態變量;
在程序啟動時分配,生命周期貫穿整個程序;
被所有任務和中斷共享。
堆區 (Heap):動態分配的內存(如?
malloc
?/?pvPortMalloc
)。棧區 (Stack):
主棧/中斷棧:用于 ISR 和內核調度;
任務棧:每個任務獨立擁有,用于存放局部變量和上下文。
結論:
全局變量存儲在全局數據區,是共享資源,訪問時需使用臨界區、信號量、互斥鎖等機制進行保護。
五、volatile
?關鍵字的作用
問題背景:
在編譯器優化的情況下,可能會將變量緩存在寄存器中,導致多任務環境中讀取到舊值。
示例代碼(無?volatile
):
c
int g_calc_end = 0;// Writer 任務 void vWriterTask(void *pvParameters) {while(1) {g_calc_end = 1; // 修改全局變量} }// Reader 任務 void vReaderTask(void *pvParameters) {while(1) {if (g_calc_end == 1) { // 判斷全局變量// 執行操作g_calc_end = 0;}vTaskDelay(1);} }
問題流程:
Reader 任務第一次讀取?
g_calc_end
?到寄存器;編譯器優化后,后續判斷直接使用寄存器中的值(不再從內存讀取);
Writer 任務修改了內存中的?
g_calc_end
;Reader 任務仍然使用寄存器中的舊值,導致判斷錯誤。
解決方案:使用?volatile
c
volatile int g_calc_end = 0;
volatile
?的作用:
告訴編譯器該變量是“易變的”;
禁止對其進行優化:
每次讀取必須從內存中重新加載;
每次寫入必須立即寫回內存。
六、補充與總結
疏漏補充:
原子操作:某些架構提供原子指令(如?
__atomic_inc
),可避免數據競爭;臨界區保護:使用開關中斷、調度器鎖、互斥量等方法保護共享資源;
任務通信機制:除了全局變量,還可使用隊列、事件組、信號量等進行任務間同步與通信;
內存屏障:在多核系統中,可能需要使用內存屏障指令確保內存訪問順序。
總結圖示:
(見原筆記中的圖片,圖示展示了同步與互斥的機制和資源訪問流程)
七、最佳實踐建議
盡量避免使用全局變量,優先使用 RTOS 提供的通信機制;
若必須使用共享資源,務必使用互斥鎖或信號量進行保護;
對于可能被異步修改的變量,必須使用?
volatile
?聲明;在臨界區中盡量減少操作時間,避免影響系統實時性;
合理使用任務阻塞機制,避免忙等待浪費 CPU 資源。