信號量Semaphores
和信號類似,信號量也是一種同步多個線程的方式,簡單來講,信號量就是裝有一些令牌的容器。當一個線程在執行過程中,就可能遇到一個系統調用來獲取信號量令牌,如果這個信號量包含多個令牌,線程就會繼續執行,同時信號量令牌的數量就會減一。如果此時信號量中沒有令牌,線程就會被置於等待狀態,直到出現一個可用的令牌。在線程執行的任何位置,它都可以給信號量增加一個令牌。
信號量用來幫助訪問程序資源,在一個線程允許訪問一個信號量之前,它必須擁有一個令牌。如果沒有令牌可用,它就必須等待,當線程使用完資源時,它就必須釋放令牌。
上圖揭示了兩個線程如何使信號量同步。首先,必須創建一個信號量,並初始化令牌數目,在上圖中,信號量初始化令牌數目為1。當兩個線程運行到某一點時就試圖從信號量中請求一個令牌,圖中第一個線程到達這個點,成功獲取一個令牌,然后繼續執行,第二個線程也試圖獲取一個令牌,但是當前信號量為空,所以它暫停執行,並進入等待狀態,直到信號量中有令牌可用。
與此同時,執行中的線程可以釋放令牌給信號量,一旦釋放完成,等待中的線程就會獲取令牌,並離開等待狀態進入准備狀態。緊接着調度器就會把它調度到運行狀態去執行剩下的代碼。
因為信號量保含較多的系統調用,所以想一次性全部理解有些難度,在本節,我們將首先看看如何給系統添加信號量,然后了解一下常用的信號量應用。
在使用信號量之前,你必須先聲明一個信號量容器:
osSemaphoreId sem1;
osSemaphoreDef(sem1);
- 1
- 2
然后在線程里給信號量容器初始化一些令牌:
sem1 = osSemaphoreCreate(osSemaphore(sem1), SIX_TOKENS);
- 1
有一點比較重要,就是在線程運行的過程中令牌既可以被創建也可以被銷毀,舉個例子,你可以初始化一個信號量,擁有0個令牌,然后用一個線程給這個信號量創建一些令牌,再使用另一個線程移除它們,這樣一來,你就可以設計線程,既可以充當生產者的線程,也可以充當消費者的線程。
一旦信號量被創建,令牌就可能被獲取,並以類似事件標志的方式發送給信號量,os_sem_wait調用來阻塞線程,直到有信號量令牌可用,類似os_event_wait,當然,在這個調用中同樣擁有超時機制,超時初始值是0xFFFF。
osStatus osSemaphoreWait(osSemaphoreId semaphore_id, uint32_t millisec);
- 1
一旦線程完成對信號量資源的使用,它就可以給信號量容器發送一個令牌:
osStatus osSemaphoreRelease(osSemaphoreId semaphore_id);
- 1
練習:信號量的信號傳輸
在這個練習中,我們將看到如何配置一個信號量,並使用它在兩個任務間發送信號。
打開Pack Installer,選擇”Ex 9 Semaphore Signaling”,然后復制到你的指定路徑。
首先創建一個信號量sem1,然后給它初始化0個令牌:
osSemaphoreId sem1;
osSemaphoreDef(sem1);
int main(void){ sem1 = osSemaphoreCreate(osSemaphore(sem1), 0);
- 1
- 2
- 3
- 4
第一個線程等待一個令牌被發送到信號量容器:
void led_Thread1(void const *argument){ for(;;){ osSemaphoreWait(sem1, osWaitForever); LED_On(1); osDelay(500); LED_Off(1); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
與此同時,第二個線程周期性的給信號量發送令牌:
void led_Thread2(void const *argument){ for(;;){ LED_On(2); osSemaphoreRelease(sem1); osDelay(500); LED_Off(2); osDelay(500); } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
編譯工程並啟動仿真環境。
在led_Thread2任務設置斷點:
運行代碼,運行到斷點,觀察線程狀態:
現在led_thread1被阻塞,等待從信號量獲取一個令牌,線程1的優先級比線程2高,所以一旦令牌放入信號量,線程1就會立即進入准備狀態,搶占低優先級線程,並隨后啟動運行。當運行到osSemaphoreWait()調用后,它又再次阻塞。
現在單步運行(F10),觀察線程及信號量的行為。