RT-Thread移植到S5P4418(四):線程同步

同步是指按預定的先後次序進行運行,線程同步是指多個線程通過特定的機制(如互斥量,事件對象,臨界區)來控制線程之間的執行順序,也可以說是在線程之間通過同步建立起執行順序的關係,如果沒有同步,那線程之間將是無序的。

線程的同步方式有很多種,其核心思想都是:在訪問臨界區的時候只允許一個 (或一類) 線程運行

RT-Thread實現了三種線程間同步方式,信號量(semaphore)、互斥量(mutex)、和事件集(event)。


信號量

信號量可以實現多個同類資源的多線程互斥和同步。
特性

  • 不支持所有權,所有線程都可以操作。
  • 遞歸訪問可能造成死鎖。
  • 二值信號量類似互斥量,可能產生優先級反轉。

使用場景

  • 線程同步。當持有信號量的線程完成它處理的工作時,釋放這個信號量,可以把等待在這個信號量上的線程喚醒,讓它執行下一部分工作。這類場合也可以看成把信號量用於工作完成標誌:持有信號量的線程完成它自己的工作,然後通知等待該信號量的線程繼續下一部分工作。
  • 。二值信號量,保護臨界區資源。
  • 中斷與線程的同步。中斷服務例程需要通知線程進行相應的數據處理。中斷與線程間的互斥不能採用信號量(鎖)的方式,而應採用開關中斷的方式。
  • 資源計數。例如生產者與消費者,生產者可以對信號量進行多次釋放,而後消費者被調度到時能夠一次處理多個信號量資源。
信號量控制塊

信號量的值對應了信號量對象的實例數目、資源數目,假如信號量值爲 5,則表示共有 5 個信號量實例(資源)可以被使用,當信號量實例數目爲零時,再申請該信號量的線程就會被掛起在該信號量的等待隊列上,等待可用的信號量實例(資源)。

struct rt_ipc_object{
    struct rt_object parent;

    rt_list_t suspend_thread;   /* 被掛起線程 */
};
struct rt_semaphore
{
    struct rt_ipc_object parent;                        /**< inherit from ipc_object */

    rt_uint16_t          value;                         /**< value of semaphore. */
};
typedef struct rt_semaphore *rt_sem_t;

在這裏插入圖片描述

信號量初始化

sem->parent.parent.flag信號量標誌參數決定了當信號量不可用時,多個線程等待的排隊方式。當選擇 RT_IPC_FLAG_FIFO(先進先出)方式時,那麼等待線程隊列將按照先進先出的方式排隊,先進入的線程將先獲得等待的信號量;當選擇 RT_IPC_FLAG_PRIO(優先級等待)方式時,等待線程隊列將按照優先級進行排隊,優先級高的等待線程將先獲得等待的信號量。

rt_err_t rt_sem_init(rt_sem_t sem, const char *name, rt_uint32_t value, rt_uint8_t flag)
{
    rt_object_init(&(sem->parent.parent), RT_Object_Class_Semaphore, name);

    rt_ipc_object_init(&(sem->parent));

    sem->value = value;

    sem->parent.parent.flag = flag;

    return RT_EOK;
}
獲取信號量

線程通過 rt_sem_take() 來獲取信號量資源實例。當信號量值大於0時,線程獲得信號量,同時該信號量值減1;當信號量值等於0時,表示資源不可用,線程通過time參數立即返回或掛起。

釋放信號量

釋放信號量時,先判斷是否有掛起線程。有則喚起一個,信號量值不變化;無則信號量值加一。

工程文件

線程2釋放信號量,線程1獲取。


互斥量

互斥量只能用於單個資源的互斥訪問。一種特殊的二值信號量。
特性

  • 支持所有權,只有擁有該互斥量的線程才能操作。
  • 支持遞歸訪問。
  • 通過優先級繼承算法降低優先級反轉問題產生的影響。

使用場景

  • 線程多次持有互斥量的情況下。這樣可以避免同一線程多次遞歸持有而造成死鎖的問題。
  • 可能會由於多線程同步而造成優先級翻轉的情況。
互斥量控制塊
struct rt_mutex
{
    struct rt_ipc_object parent;                /* 繼承自 ipc_object 類 */

    rt_uint16_t          value;                 /* 互斥量的值 */
    rt_uint8_t           original_priority;     /* 持有線程的原始優先級 */
    rt_uint8_t           hold;                  /* 持有線程的持有次數   */
    struct rt_thread    *owner;                 /* 當前擁有互斥量的線程 */
};
typedef struct rt_mutex* rt_mutex_t;

在這裏插入圖片描述

互斥量初始化
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag){
    RT_ASSERT(mutex != RT_NULL);

    rt_object_init(&mutex->parent.parent, RT_Object_Class_Mutex, name);

    rt_ipc_object_init(&mutex->parent);

    mutex->parent.parent.flag = flag;
    mutex->value = 1;
    mutex->original_priority = RT_THREAD_PRIORITY_MAX - 1;
    mutex->hold = 0;
    mutex->owner = RT_NULL;

    return RT_EOK;
}
獲取互斥量

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t time)
線程獲取了互斥量,那麼線程就有了對該互斥量的所有權,即某一個時刻一個互斥量只能被一個線程持有。
如果互斥量已經被當前線程控制,則持有數mutex.hold加一。如果互斥量被其他線程佔有,則當前線程在互斥量上掛起等待,直到其他線程釋放互斥量或等待時間超時,同時,如果當前線程的優先級高於互斥量擁有者的優先級,會發生優先級繼承。

釋放互斥量

rt_err_t rt_mutex_release(rt_mutex_t mutex)
只有互斥量的擁有者才能釋放互斥量,持有數mutex.hold減一。如果持有數變成零,則判斷是否有線程掛起,有則喚起。

工程文件

thread1和thread2對全局變量number進行加1操作,都執行200000次,有mutex保護時,結果正確,開銷大。


事件集

事件集用一個 32 位無符號整型變量來表示,變量的每一位代表一個事件。

事件集控制塊
struct rt_event
{
    struct rt_ipc_object parent;     /**< inherit from ipc_object */

    rt_uint32_t          set;        /* 事件集合,每一 bit 表示 1 個事件,bit 位的值可以標記某事件是否發生 */
};
typedef struct rt_event *rt_event_t;

在這裏插入圖片描述

發送事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set)
發送事件時遍歷事件集的掛起線程,判斷是否有線程的事件激活要求與當前 event 對象事件標誌值匹配,如果有,則喚醒該線程。

接收事件

rt_err_t rt_event_recv(rt_event_t event, rt_uint32_t set, rt_uint8_t option, rt_int32_t timeout, rt_uint32_t *recved)
或者叫等待事件觸發。系統首先根據 set 參數和接收選項 option 來判斷它要接收的事件是否發生,如果已經發生,則根據參數 option 上是否設置有 RT_EVENT_FLAG_CLEAR 來決定是否重置事件的相應標誌位,然後返回。如果沒有發生,則把等待的 set 和 option 參數填入線程本身的結構中,然後把線程掛起在此事件上,直到其等待的事件滿足條件或等待時間超過指定的超時時間。

工程文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章