1. 程式人生 > 實用技巧 >RTThread學習筆記——執行緒間通訊學習

RTThread學習筆記——執行緒間通訊學習

由通訊提出的問題

  在裸機程式設計的過程中,我們經常會遇到函式需要另一些函式的資料資訊,也就是通訊,這時我們會怎麼做呢?進行裸機開發的同學肯定都會說:使用全域性變數,通過指標實現之類。使用全域性變數快捷且高效。  

  但是在RTOS系統中,這會遇到一些問題:怎樣防止許多執行緒同時進行對這個變數的訪問?怎樣觀測通訊是否已經發生,從而進行通訊之後的工作?(例如:進行優先順序轉換,或者進行資料的處理)如果有個執行緒比較磨蹭,通訊遲遲不發生,CPU豈不是要尷尬地等待好久。。

  對於這些問題,作業系統給出了自己的通訊方法與函式供使用者呼叫,當然,也最好使用作業系統的通訊機制。

  

  我們開啟RTT 的IDE,然後開啟核心的Setting,會看到RTT的一些設定:

  

  可以看到,執行緒間通訊一欄,這就是RTT給開發者提供的通訊機制,在實際開發中,這些機制可以滿足絕大部分需求。RTT系統預設打開了訊號量,互斥量,事件集,郵箱,訊息佇列五種機制。


訊號量:

二值訊號量:

  在我們的裸機開發過程中,經常會使用一個變數:Flag,大多數時候,這個叫做Flag的變數會在0和1之間反覆橫跳,用於標記某一件事情是否發生,並經常被用於中斷。這個Flag就是一個訊號量,實際上,它是我們最常使用的通訊方式之一。

  在RTT系統中,訊號量會被獲取或者釋放(由獲取/釋放函式實現),當它被獲取時,就為0,當它被釋放,就為1。這樣的訊號量被稱為二值訊號量。

  舉一個例子(虛擬碼):

static void xxx1()
{
    while(1)
    {
         /*一個感測器採集一個特別複雜的資料,需要3S,不需要CPU參與*/
         xxx
    }
}

static void xxx2()
{
    while(1)
    {
         /*將這個資料交給CPU進行分析,資料無效就捨棄*/   
         xxx
      rt_thread_mdelay(xx)  } }

  在這個例子中,有兩個執行緒,執行緒2中,CPU一直在等待資料,但資料卻總是無效的(因為感測器還沒采集到資料),這讓CPU一直在進行資料驗證,雖然CPU中間進行了一些延時,但仍然在無效資料上浪費了時間,這時,就需要訊號量出馬了

  改良後資料如下(虛擬碼):  

rt_sem_create(xxx);    //訊號量建立函式,引數中預設為1,表示是釋放的,具體引數不贅述

static void xxx1()
{
    while(1)
    {
         rt_sem_take(xxx)    //獲取訊號量,這時訊號量被置0

         /*一個感測器採集一個特別複雜的資料,需要3S,不需要CPU參與*/
         xxx

         rt_sem_release(xxx)  //釋放訊號量,這時訊號量被置1
    }
}

static void xxx2()
{
    while(1)
    {
         rt_sem_take(xxx)    //獲取訊號量,當訊號量已經被其他執行緒獲取,就進入阻塞態等待,等待時間自定,這裡不贅述

         /*將這個資料交給CPU進行分析,資料無效就捨棄*/   
         xxx

         rt_sem_release(xxx)  //釋放訊號量
   rt_thread_mdelay(xx) 
    }
}        

  這裡我們看到,當感測器開始工作時,訊號量已經被獲取了,這時進入執行緒二想要處理資料,就會進入阻塞態,讓出CPU去做其他的事情,當感測器獲得資料後,就會釋放訊號量,這時,執行緒二就從阻塞態中退出,從而去處理有效的資料。(注意:獲取訊號量進行相關操作後應立刻釋放訊號量,如果不進行釋放,可能會導致其他執行緒無法執行)

  經過這一番操作,無效資料不會被一次又一次的處理了,而是在感測器獲得資料後,CPU直接去處理有效的資料,節省了許多無效操作。

計數型訊號量:

  計數型訊號量與二值訊號量類似,只不過範圍從二值的[0,1],變為了[0,65535],這樣它就可以被多個執行緒獲取,獲取的最大數目由使用者自定,使用的系統函式與二值訊號量相同。

  計數訊號量經常被用於控制一些公共資源的訪問數量,例如一個資源最多可以被x個執行緒同時訪問,多於x時效率大大下降或者會出現錯誤(有點像網站伺服器限制訪客數量)。


互斥量:

 互斥量是一種特殊的二值訊號量,其與二值訊號量的最大區別為引入了優先順序繼承機制。

  優先順序繼承機制:如果一個高優先順序的執行緒去申請一個已經被低優先順序獲得的互斥量,高優先順序會進入到阻塞態,而持有這個互斥量的執行緒地位會瞬間與高優先順序一樣,取得高優先順序相同的優先等級,這就是“優先順序繼承”

  這樣有什麼好處呢?這裡依然是一個栗子。

static void HIGH()
{
    xxx
}

static void MIDDLE()
{
    xxx
}

static void LOW()
{
    xxx
}

  假設有三個執行緒,優先度分別為高,中,低。理所當然,開發者希望高優先順序的越快處理越好,低優先順序的則不是很緊要。

  這時,如果有個二值訊號量,被低優先順序(LOW)獲取。然後高優先順序(HIGH)的也想要獲取這個訊號量,於是進入了阻塞等待。

  但是當LOW準備用CPU執行時,MIDDLE卻發話了:我有更緊要的任務,你讓開。LOW小弟就被擠到了一邊,畢竟它是優先順序最低的。

  這就產生了一個問題,開發者最希望執行的HIGH在等待訊號量,而MIDDLE卻在執行(假設MIDDLE和HIGH無關聯,此時HIGH就要等待MIDDLE執行+LOW執行,而不是隻等待LOW執行),LOW在等待CPU讓出。這顯然違背了開發者“高優先順序越快越好”的想法。

  

  怎樣解決這個問題,這時就引入互斥量來解決這個問題,當HIGH進入阻塞態之後,LOW瞬間“繼承”了HIGH的優先順序,LOW小弟也暫時體驗了當大哥的感覺,並把MIDDLE擠到一邊,快速的處理起了資料,並在處理完後變回原來的優先順序,將互斥量釋放,HIGH得以繼續執行。這樣,通過暫時提高LOW的優先順序,開發者所希望的HIGH處理越快越好的想法得以實現。

(暫時記錄訊號量和互斥量的學習筆記,文中可能有些理解和想法有略微的偏差,請注意=))