RT-Thread 線程間同步【信號量、互斥量、事件集】

線程間同步

  • 一、信號量
    • 1. 創建信號量
    • 2. 獲取信號量
    • 3. 釋放信號量
    • 4. 刪除信號量
    • 5. 代碼示例
  • 二、互斥量
    • 1. 創建互斥量
    • 2. 獲取互斥量
    • 3. 釋放互斥量
    • 4. 刪除互斥量
    • 5. 代碼示例
  • 三、事件集
    • 1. 創建事件集
    • 2. 發送事件
    • 3. 接收事件
    • 4. 刪除事件集
    • 5. 代碼示例


簡單來說,同步就是多個線程同時訪問一塊內存,好比如 一個線程向指定內存中寫入一個數據,另一個線程就從該內存中讀取數據,這就是“同步”。
在這里插入圖片描述
線程的同步方式有很多種,其核心思想都是:在訪問臨界區的時候只允許一個 (或一類) 線程運行

(以下都以動態創建方式介紹)

一、信號量

信號量是一種輕型的用于解決線程間同步問題的內核對象,線程可以獲取或釋放它,從而達到同步或互斥的目的。

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

1. 創建信號量

當創建一個信號量時,內核首先創建一個信號量控制塊,然后對該控制塊進行基本的初始化工作

 rt_sem_t rt_sem_create(const char *name,rt_uint32_t value,rt_uint8_t flag);

在這里插入圖片描述
注: RT_IPC_FLAG_FIFO(先進先出)方式時,那么等待線程隊列將按照先進先出的方式排隊,先進入的線程將先獲得等待的信號量;當選擇 RT_IPC_FLAG_PRIO(優先級等待)方式時,等待線程隊列將按照優先級進行排隊,優先級高的等待線程將先獲得等待的信號量。。

2. 獲取信號量

線程通過獲取信號量來獲得信號量資源實例,當信號量值大于零時,線程將獲得信號量,并且相應的信號量值會減 1

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time);

在這里插入圖片描述

在調用這個函數時,如果信號量的值等于零,那么說明當前信號量資源實例不可用,申請該信號量的線程將根據 time 參數的情況選擇直接返回、或掛起等待一段時間、或永久等待,直到其他線程或中斷釋放該信號量。如果在參數 time 指定的時間內依然得不到信號量,線程將超時返回,返回值是 - RT_ETIMEOUT。

3. 釋放信號量

釋放信號量可以喚醒掛起在該信號量上的線程

rt_err_t rt_sem_release(rt_sem_t sem);

在這里插入圖片描述

當信號量的值等于零時,并且有線程等待這個信號量時,釋放信號量將喚醒等待在該信號量線程隊列中的第一個線程,由它獲取信號量;否則將把信號量的值加 1

4. 刪除信號量

系統不再使用信號量時,可通過刪除信號量以釋放系統資源,適用于動態創建的信號量

rt_err_t rt_sem_delete(rt_sem_t sem);

在這里插入圖片描述

調用這個函數時,系統將刪除這個信號量。如果刪除該信號量時,有線程正在等待該信號量,那么刪除操作會先喚醒等待在該信號量上的線程(等待線程的返回值是 - RT_ERROR),然后再釋放信號量的內存資源

5. 代碼示例

這是一個信號量使用例程,該例程創建了一個動態信號量,初始化兩個線程,一個線程發送信號量,一個線程接收到信號量后,執行相應的操作

#include <rtthread.h>#define THREAD_PRIORITY         25
#define THREAD_TIMESLICE        5/* 指向信號量的指針 */
static rt_sem_t dynamic_sem = RT_NULL;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread1_entry(void *parameter)
{static rt_uint8_t count = 0;while(1){if(count <= 100){count++;}elsereturn;/* count 每計數 10 次,就釋放一次信號量 */if(0 == (count % 10)){rt_kprintf("t1 release a dynamic semaphore.\n");rt_sem_release(dynamic_sem);}}
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread2_entry(void *parameter)
{static rt_err_t result;static rt_uint8_t number = 0;while(1){/* 永久方式等待信號量,獲取到信號量,則執行 number 自加的操作 */result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);if (result != RT_EOK){rt_kprintf("t2 take a dynamic semaphore, failed.\n");rt_sem_delete(dynamic_sem);return;}else{number++;rt_kprintf("t2 take a dynamic semaphore. number = %d\n" ,number);}}
}/* 信號量示例的初始化 */
int semaphore_sample(void)
{/* 創建一個動態信號量,初始值是 0 */dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);if (dynamic_sem == RT_NULL){rt_kprintf("create dynamic semaphore failed.\n");return -1;}else{rt_kprintf("create done. dynamic semaphore value = 0.\n");}rt_thread_init(&thread1,"thread1",rt_thread1_entry,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),THREAD_PRIORITY, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",rt_thread2_entry,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY-1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}
/* 導出到 msh 命令列表中 */
MSH_CMD_EXPORT(semaphore_sample, semaphore sample);

運行結果如下:

 \ | /
- RT -     Thread Operating System/ | \     3.1.0 build Aug 27 20182006 - 2018 Copyright by rt-thread team
msh >semaphore_sample
create done. dynamic semaphore value = 0.
msh >t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 1
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 2
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 3
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 4
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 5
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 6
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 7
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 8
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 9
t1 release a dynamic semaphore.
t2 take a dynamic semaphore. number = 10

二、互斥量

互斥量又叫相互排斥的信號量,是一種特殊的二值信號量。互斥量類似于只有一個車位的停車場:當有一輛車進入的時候,將停車場大門鎖住,其他車輛在外面等候。當里面的車出來時,將停車場大門打開,下一輛車才可以進入。

互斥量和信號量不同的是:擁有互斥量的線程擁有互斥量的所有權,互斥量支持遞歸訪問且能防止線程優先級翻轉;并且互斥量只能由持有線程釋放,而信號量則可以由任何線程釋放。

互斥量的狀態只有兩種,開鎖或閉鎖(兩種狀態值)。當有線程持有它時,互斥量處于閉鎖狀態,由這個線程獲得它的所有權。相反,當這個線程釋放它時,將對互斥量進行開鎖,失去它的所有權。當一個線程持有互斥量時,其他線程將不能夠對它進行開鎖或持有它,持有該互斥量的線程也能夠再次獲得這個鎖而不被掛起

RT-Thread 操作系統中,互斥量可以解決優先級翻轉問題,實現的是優先級繼承協議 (Sha, 1990)。優先級繼承是通過在線程 A 嘗試獲取共享資源而被掛起的期間內,將線程 C 的優先級提升到線程 A 的優先級別,從而解決優先級翻轉引起的問題。這樣能夠防止 C(間接地防止 A)被 B 搶占,如下圖所示。優先級繼承是指,提高某個占有某種資源的低優先級線程的優先級,使之與所有等待該資源的線程中優先級最高的那個線程的優先級相等,然后執行,而當這個低優先級線程釋放該資源時,優先級重新回到初始設定。因此,繼承優先級的線程避免了系統資源被任何中間優先級的線程搶占。

1. 創建互斥量

創建一個互斥量時,內核首先創建一個互斥量控制塊,然后完成對該控制塊的初始化工作

rt_mutex_t rt_mutex_create (const char* name, rt_uint8_t flag);

在這里插入圖片描述
注: 互斥量的 flag 標志已經作廢,無論用戶選擇 RT_IPC_FLAG_PRIO 還是 RT_IPC_FLAG_FIFO,內核均按照 RT_IPC_FLAG_PRIO 處理

2. 獲取互斥量

線程獲取了互斥量,那么線程就有了對該互斥量的所有權,即某一個時刻一個互斥量只能被一個線程持有

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

在這里插入圖片描述

如果互斥量沒有被其他線程控制,那么申請該互斥量的線程將成功獲得該互斥量。如果互斥量已經被當前線程線程控制,則該互斥量的持有計數加 1,當前線程也不會掛起等待。如果互斥量已經被其他線程占有,則當前線程在該互斥量上掛起等待,直到其他線程釋放它或者等待時間超過指定的超時時間

3. 釋放互斥量

當線程完成互斥資源的訪問后,應盡快釋放它占據的互斥量,使得其他線程能及時獲取該互斥量

rt_err_t rt_mutex_release(rt_mutex_t mutex);

在這里插入圖片描述

使用該函數接口時,只有已經擁有互斥量控制權的線程才能釋放它,每釋放一次該互斥量,它的持有計數就減 1。當該互斥量的持有計數為零時(即持有線程已經釋放所有的持有操作),它變為可用,等待在該信號量上的線程將被喚醒。如果線程的運行優先級被互斥量提升,那么當互斥量被釋放后,線程恢復為持有互斥量前的優先級

4. 刪除互斥量

當不再使用互斥量時,通過刪除互斥量以釋放系統資源,適用于動態創建的互斥量

rt_err_t rt_mutex_delete (rt_mutex_t mutex);

在這里插入圖片描述
當刪除一個互斥量時,所有等待此互斥量的線程都將被喚醒,等待線程獲得的返回值是 - RT_ERROR。然后系統將該互斥量從內核對象管理器鏈表中刪除并釋放互斥量占用的內存空間

5. 代碼示例

這是一個互斥量的應用例程,互斥鎖是一種保護共享資源的方法。當一個線程擁有互斥鎖的時候,可以保護共享資源不被其他線程破壞。下面用一個例子來說明,有兩個線程:線程 1 和線程 2,線程 1 對 2 個 number 分別進行加 1 操作;線程 2 也對 2 個 number 分別進行加 1 操作,使用互斥量保證線程改變 2 個 number 值的操作不被打斷

互斥量例程

#include <rtthread.h>#define THREAD_PRIORITY         8
#define THREAD_TIMESLICE        5/* 指向互斥量的指針 */
static rt_mutex_t dynamic_mutex = RT_NULL;
static rt_uint8_t number1,number2 = 0;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;
static void rt_thread_entry1(void *parameter)
{while(1){/* 線程 1 獲取到互斥量后,先后對 number1、number2 進行加 1 操作,然后釋放互斥量 */rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);number1++;rt_thread_mdelay(10);number2++;rt_mutex_release(dynamic_mutex);}
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;
static void rt_thread_entry2(void *parameter)
{while(1){/* 線程 2 獲取到互斥量后,檢查 number1、number2 的值是否相同,相同則表示 mutex 起到了鎖的作用 */rt_mutex_take(dynamic_mutex, RT_WAITING_FOREVER);if(number1 != number2){rt_kprintf("not protect.number1 = %d, mumber2 = %d \n",number1 ,number2);}else{rt_kprintf("mutex protect ,number1 = mumber2 is %d\n",number1);}number1++;number2++;rt_mutex_release(dynamic_mutex);if(number1>=50)return;}
}/* 互斥量示例的初始化 */
int mutex_sample(void)
{/* 創建一個動態互斥量 */dynamic_mutex = rt_mutex_create("dmutex", RT_IPC_FLAG_PRIO);if (dynamic_mutex == RT_NULL){rt_kprintf("create dynamic mutex failed.\n");return -1;}rt_thread_init(&thread1,"thread1",rt_thread_entry1,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),THREAD_PRIORITY, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",rt_thread_entry2,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY-1, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}/* 導出到 MSH 命令列表中 */
MSH_CMD_EXPORT(mutex_sample, mutex sample);

運行結果如下:

 \ | /
- RT -     Thread Operating System/ | \     3.1.0 build Aug 24 20182006 - 2018 Copyright by rt-thread team
msh >mutex_sample
msh >mutex protect ,number1 = mumber2 is 1
mutex protect ,number1 = mumber2 is 2
mutex protect ,number1 = mumber2 is 3
mutex protect ,number1 = mumber2 is 4
…
mutex protect ,number1 = mumber2 is 48
mutex protect ,number1 = mumber2 is 49

防止優先級翻轉特性例程

#include <rtthread.h>/* 指向線程控制塊的指針 */
static rt_thread_t tid1 = RT_NULL;
static rt_thread_t tid2 = RT_NULL;
static rt_thread_t tid3 = RT_NULL;
static rt_mutex_t mutex = RT_NULL;#define THREAD_PRIORITY       10
#define THREAD_STACK_SIZE     512
#define THREAD_TIMESLICE    5/* 線程 1 入口 */
static void thread1_entry(void *parameter)
{/* 先讓低優先級線程運行 */rt_thread_mdelay(100);/* 此時 thread3 持有 mutex,并且 thread2 等待持有 mutex *//* 檢查 thread2 與 thread3 的優先級情況 */if (tid2->current_priority != tid3->current_priority){/* 優先級不相同,測試失敗 */rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);rt_kprintf("test failed.\n");return;}else{rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);rt_kprintf("test OK.\n");}
}/* 線程 2 入口 */
static void thread2_entry(void *parameter)
{rt_err_t result;rt_kprintf("the priority of thread2 is: %d\n", tid2->current_priority);/* 先讓低優先級線程運行 */rt_thread_mdelay(50);/** 試圖持有互斥鎖,此時 thread3 持有,應把 thread3 的優先級提升* 到 thread2 相同的優先級*/result = rt_mutex_take(mutex, RT_WAITING_FOREVER);if (result == RT_EOK){/* 釋放互斥鎖 */rt_mutex_release(mutex);}
}/* 線程 3 入口 */
static void thread3_entry(void *parameter)
{rt_tick_t tick;rt_err_t result;rt_kprintf("the priority of thread3 is: %d\n", tid3->current_priority);result = rt_mutex_take(mutex, RT_WAITING_FOREVER);if (result != RT_EOK){rt_kprintf("thread3 take a mutex, failed.\n");}/* 做一個長時間的循環,500ms */tick = rt_tick_get();while (rt_tick_get() - tick < (RT_TICK_PER_SECOND / 2)) ;rt_mutex_release(mutex);
}int pri_inversion(void)
{/* 創建互斥鎖 */mutex = rt_mutex_create("mutex", RT_IPC_FLAG_PRIO);if (mutex == RT_NULL){rt_kprintf("create dynamic mutex failed.\n");return -1;}/* 創建線程 1 */tid1 = rt_thread_create("thread1",thread1_entry,RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY - 1, THREAD_TIMESLICE);if (tid1 != RT_NULL)rt_thread_startup(tid1);/* 創建線程 2 */tid2 = rt_thread_create("thread2",thread2_entry,RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY, THREAD_TIMESLICE);if (tid2 != RT_NULL)rt_thread_startup(tid2);/* 創建線程 3 */tid3 = rt_thread_create("thread3",thread3_entry,RT_NULL,THREAD_STACK_SIZE,THREAD_PRIORITY + 1, THREAD_TIMESLICE);if (tid3 != RT_NULL)rt_thread_startup(tid3);return 0;
}/* 導出到 msh 命令列表中 */
MSH_CMD_EXPORT(pri_inversion, prio_inversion sample);

運行結果如下:

 \ | /
- RT -     Thread Operating System/ | \     3.1.0 build Aug 27 20182006 - 2018 Copyright by rt-thread team
msh >pri_inversion
the priority of thread2 is: 10
the priority of thread3 is: 11
the priority of thread2 is: 10
the priority of thread3 is: 10
test OK.

三、事件集

事件集主要用于線程間的同步,與信號量不同,它的特點是可以實現一對多,多對多的同步。即一個線程與多個事件的關系可設置為:其中任意一個事件喚醒線程,或幾個事件都到達后才喚醒線程進行后續的處理;同樣,事件也可以是多個線程同步多個事件。這種多個事件的集合可以用一個 32 位無符號整型變量來表示,變量的每一位代表一個事件,線程通過 “邏輯與” 或“邏輯或”將一個或多個事件關聯起來,形成事件組合。事件的 “邏輯或” 也稱為是獨立型同步,指的是線程與任何事件之一發生同步;事件 “邏輯與” 也稱為是關聯型同步,指的是線程與若干事件都發生同步。

RT-Thread 定義的事件集有以下特點

  • 事件只與線程相關,事件間相互獨立:每個線程可擁有 32 個事件標志,采用一個 32 bit 無符號整型數進行記錄,每一個 bit 代表一個事件;
  • 事件僅用于同步,不提供數據傳輸功能;
  • 事件無排隊性,即多次向線程發送同一事件 (如果線程還未來得及讀走),其效果等同于只發送一次。

在 RT-Thread 中,每個線程都擁有一個事件信息標記,它有三個屬性,分別是 RT_EVENT_FLAG_AND(邏輯與),RT_EVENT_FLAG_OR(邏輯或)以及 RT_EVENT_FLAG_CLEAR(清除標記)。當線程等待事件同步時,可以通過 32 個事件標志和這個事件信息標記來判斷當前接收的事件是否滿足同步條件。
在這里插入圖片描述
線程 #1 的事件標志中第 1 位和第 30 位被置位,如果事件信息標記位設為邏輯與,則表示線程 #1 只有在事件 1 和事件 30 都發生以后才會被觸發喚醒,如果事件信息標記位設為邏輯或,則事件 1 或事件 30 中的任意一個發生都會觸發喚醒線程 #1。如果信息標記同時設置了清除標記位,則當線程 #1 喚醒后將主動把事件 1 和事件 30 清為零,否則事件標志將依然存在(即置 1)

1. 創建事件集

當創建一個事件集時,內核首先創建一個事件集控制塊,然后對該事件集控制塊進行基本的初始化

rt_event_t rt_event_create(const char* name, rt_uint8_t flag);

在這里插入圖片描述
調用該函數接口時,系統會從對象管理器中分配事件集對象,并初始化這個對象,然后初始化父類 IPC 對象

2. 發送事件

發送事件函數可以發送事件集中的一個或多個事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

在這里插入圖片描述

使用該函數接口時,通過參數 set 指定的事件標志來設定 event 事件集對象的事件標志值,然后遍歷等待在 event 事件集對象上的等待線程鏈表,判斷是否有線程的事件激活要求與當前 event 對象事件標志值匹配,如果有,則喚醒該線程

3. 接收事件

內核使用 32 位的無符號整數來標識事件集,它的每一位代表一個事件,因此一個事件集對象可同時等待接收 32 個事件,內核可以通過指定選擇參數 “邏輯與” 或“邏輯或”來選擇如何激活線程,使用 “邏輯與” 參數表示只有當所有等待的事件都發生時才激活線程,而使用 “邏輯或” 參數則表示只要有一個等待的事件發生就激活線程

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 來決定是否重置事件的相應標志位,然后返回(其中 recved 參數返回接收到的事件);如果沒有發生,則把等待的 set 和 option 參數填入線程本身的結構中,然后把線程掛起在此事件上,直到其等待的事件滿足條件或等待時間超過指定的超時時間。如果超時時間設置為零,則表示當線程要接受的事件沒有滿足其要求時就不等待,而直接返回 - RT_ETIMEOUT
在這里插入圖片描述
option 的值可取:

/* 選擇 邏輯與 或 邏輯或 的方式接收事件 */
RT_EVENT_FLAG_OR
RT_EVENT_FLAG_AND/* 選擇清除重置事件標志位 */
RT_EVENT_FLAG_CLEAR

4. 刪除事件集

系統不再使用 rt_event_create() 創建的事件集對象時,通過刪除事件集對象控制塊來釋放系統資源

rt_err_t rt_event_delete(rt_event_t event);

在這里插入圖片描述

5. 代碼示例

這是事件集的應用例程,例子中初始化了一個事件集,兩個線程。一個線程等待自己關心的事件發生,另外一個線程發送事件

#include <rtthread.h>#define THREAD_PRIORITY      9
#define THREAD_TIMESLICE     5#define EVENT_FLAG3 (1 << 3)
#define EVENT_FLAG5 (1 << 5)/* 事件控制塊 */
static struct rt_event event;ALIGN(RT_ALIGN_SIZE)
static char thread1_stack[1024];
static struct rt_thread thread1;/* 線程 1 入口函數 */
static void thread1_recv_event(void *param)
{rt_uint32_t e;/* 第一次接收事件,事件 3 或事件 5 任意一個可以觸發線程 1,接收完后清除事件標志 */if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &e) == RT_EOK){rt_kprintf("thread1: OR recv event 0x%x\n", e);}rt_kprintf("thread1: delay 1s to prepare the second event\n");rt_thread_mdelay(1000);/* 第二次接收事件,事件 3 和事件 5 均發生時才可以觸發線程 1,接收完后清除事件標志 */if (rt_event_recv(&event, (EVENT_FLAG3 | EVENT_FLAG5),RT_EVENT_FLAG_AND | RT_EVENT_FLAG_CLEAR,RT_WAITING_FOREVER, &e) == RT_EOK){rt_kprintf("thread1: AND recv event 0x%x\n", e);}rt_kprintf("thread1 leave.\n");
}ALIGN(RT_ALIGN_SIZE)
static char thread2_stack[1024];
static struct rt_thread thread2;/* 線程 2 入口 */
static void thread2_send_event(void *param)
{rt_kprintf("thread2: send event3\n");rt_event_send(&event, EVENT_FLAG3);rt_thread_mdelay(200);rt_kprintf("thread2: send event5\n");rt_event_send(&event, EVENT_FLAG5);rt_thread_mdelay(200);rt_kprintf("thread2: send event3\n");rt_event_send(&event, EVENT_FLAG3);rt_kprintf("thread2 leave.\n");
}int event_sample(void)
{rt_err_t result;/* 初始化事件對象 */result = rt_event_init(&event, "event", RT_IPC_FLAG_PRIO);if (result != RT_EOK){rt_kprintf("init event failed.\n");return -1;}rt_thread_init(&thread1,"thread1",thread1_recv_event,RT_NULL,&thread1_stack[0],sizeof(thread1_stack),THREAD_PRIORITY - 1, THREAD_TIMESLICE);rt_thread_startup(&thread1);rt_thread_init(&thread2,"thread2",thread2_send_event,RT_NULL,&thread2_stack[0],sizeof(thread2_stack),THREAD_PRIORITY, THREAD_TIMESLICE);rt_thread_startup(&thread2);return 0;
}/* 導出到 msh 命令列表中 */
MSH_CMD_EXPORT(event_sample, event sample);

運行結果如下:

 \ | /
- RT -     Thread Operating System/ | \     3.1.0 build Aug 24 20182006 - 2018 Copyright by rt-thread team
msh >event_sample
thread2: send event3
thread1: OR recv event 0x8
thread1: delay 1s to prepare the second event
msh >thread2: send event5
thread2: send event3
thread2 leave.
thread1: AND recv event 0x28
thread1 leave.

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/161965.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/161965.shtml
英文地址,請注明出處:http://en.pswp.cn/news/161965.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

PDF轉成圖片

使用開源庫Apache PDFBox將PDF轉換為圖片 依賴 <dependency><groupId>org.apache.pdfbox</groupId><artifactId>fontbox</artifactId><version>2.0.4</version> </dependency> <dependency><groupId>org.apache…

DockerHub 無法訪問 - 解決辦法

背景 DockerHub 鏡像倉庫地址 https://hub.docker.com/ 突然就無法訪問了,且截至今日(2023/11)還無法訪問。 這對我們來說,還是有一些影響的: ● 雖然 DockerHub 頁面無法訪問,但是還是可以下載鏡像的,只是比較慢而已 ● 沒法通過界面查詢相關鏡像,或者維護相關鏡像了…

JAVA 使用stream流將List中的對象某一屬性創建新的List

JAVA 使用stream流將List中的對象某一屬性創建新的List 1.stream流介紹 Java Stream是Java 8引入的一種新機制&#xff0c;它可以讓我們以聲明式方式操作集合數據&#xff0c;提供了更加簡潔、優雅的集合處理方式。Stream是一個來自數據源的元素隊列&#xff0c;并支持聚合操…

【Rxjava詳解】(二) 操作符的妙用

文章目錄 接口變化操作符mapflatmapdebouncethrottleFirst()takeconcat RxJava 是一個基于 觀察者模式的異步編程庫&#xff0c;它提供了豐富的操作符來處理和轉換數據流。 操作符是 RxJava 的核心組成部分&#xff0c;它們提供了一種靈活、可組合的方式來處理數據流&#xf…

C++二分算法:得到子序列的最少操作次數

本文涉及的基礎知識點 二分查找算法合集 題目 給你一個數組 target &#xff0c;包含若干 互不相同 的整數&#xff0c;以及另一個整數數組 arr &#xff0c;arr 可能 包含重復元素。 每一次操作中&#xff0c;你可以在 arr 的任意位置插入任一整數。比方說&#xff0c;如果…

【如何學習Python自動化測試】—— 多層窗口定位

6 、 多層窗口定位 多層窗口指的是在操作系統圖形界面中&#xff0c;一個窗口被另一個窗口覆蓋的情況。在多層窗口中&#xff0c;如何定位需要操作的窗口&#xff1f; 一種常見的方法是使用操作系統提供的AltTab快捷鍵&#xff0c;可以在打開的所有窗口中快速切換焦點。如果需要…

第十三章 控制值的轉換 - 處理UTC時區指示符

文章目錄 第十三章 控制值的轉換 - 處理UTC時區指示符 第十三章 控制值的轉換 - 處理UTC時區指示符 對于支持XML的類&#xff0c;可以指定在從XML文檔導入時是否使用UTC時區指示符。同樣&#xff0c;可以指定是否在導出時包含UTC時區指示符。 為此&#xff0c;指定XMLTIMEZON…

GEE生物量碳儲量——利用sens和MK檢驗方法計算1987-2022年森林地上生物量AGB和碳儲量的時空變化特征

簡介: 本文是將之前已經處理好的森林生物量和碳儲量數據保存到GEE Assets中,然后分別將單張影像導入到代碼編輯器中,構建一個時間序列集合,并且這里需要用到的是我們給影像添加指定的時間屬性,這樣方便進行下一步的時序分析和空間預測。 首先,需要收集1987年至2022年期…

C語言實現Linux下TCP Server測試工具

Linux TCP Server測試工具代碼 實現了接受數據并輸出文本和十六制字符串 #include <stdio.h> #include<string.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <signal.h> #include <arpa/inet.h> #incl…

STM32內存介紹

ROM是一種只讀存儲器&#xff0c;經歷了從NOR Flash到NAND Flash再到現在的eMMC的發展。為了便于使用和大批量生產&#xff0c;ROM進一步分為了4種類型&#xff1a;PROM、EPROM、EEPROM和Flash。PROM只能被編程一次&#xff0c;EPROM可擦寫可編程且可達1000次&#xff0c;EEPRO…

leetcode/hot100

文章目錄 一、哈希1.兩數之和2.字母異位詞分組3.最長連續序列 二、雙指針4. 移動零5.盛最多水的容器6.三數之和7.接雨水 三、滑動窗口8.無重復字符的最長子串9.找到字符串中所有字母異位詞 四、子串10.和為 K 的子數組 一、哈希 1.兩數之和 1. 兩數之和 class Solution { pu…

規則引擎Drools使用,0基礎入門規則引擎Drools(二)高級語法

文章目錄 系列文章索引五、規則屬性1、enabled屬性2、dialect屬性3、salience屬性4、no-loop屬性5、activation-group屬性6、agenda-group屬性7、auto-focus屬性8、timer屬性9、date-effective屬性10、date-expires屬性 六、Drools高級語法1、global全局變量2、query查詢3、fun…

20231122給RK3399的挖掘機開發板適配Android12

20231122給RK3399的挖掘機開發板適配Android12 2023/11/22 9:30 主要步驟&#xff1a; rootrootrootroot-X99-Turbo:~$ tar --use-compress-programpigz -xvpf rk356x_android12_220722.tgz rootrootrootroot-X99-Turbo:~$ cd rk_android12_220722/ rootrootrootroot-X99-Tur…

rk3568 適配以太網(mac 2 mac)

rk3568 適配以太網(mac 2 mac) MDI(Media Dependent Interface)是以太網中的一種接口標準,用于連接物理層(PHY)和數據鏈路層(MAC)之間的傳輸介質。 在以太網中,MDI通常通過RJ-45插座來實現,用于連接網線和網絡設備。MDI接口提供了電氣和機械特性,使得PHY和MAC能夠正…

Python通過串口收發文件

單位內外網是隔離的,USB對拷線被禁用,安全優盤使用太費事,就想到了通過串口傳輸文件. import serial from xmodem import XMODEM import osdef Send_File(filepath, portCOM8, baudrate115200):bn os.path.basename(filepath)filesize os.stat(filepath).st_sizestrSendFile…

帶記憶的超級GPT智能體,能做飯、煮咖啡、整理家務!

隨著AI技術的快速迭代&#xff0c;Alexa、Siri、小度、天貓精靈等語音助手得到了廣泛應用。但在自然語言理解和完成復雜任務方面仍然有限。 相比文本的標準格式&#xff0c;語音充滿復雜性和多樣性&#xff08;例如&#xff0c;地方話&#xff09;,傳統方法很難適應不同用戶的…

【每日OJ —— 20.有效的括號(棧)】

每日OJ —— 20.有效的括號&#xff08;棧&#xff09; 1.題目&#xff1a;20.有效的括號&#xff08;棧&#xff09;2.方法講解2.1.解法2.1.1.算法講解2.1.2.代碼實現2.1.3.提交通過展示 1.題目&#xff1a;20.有效的括號&#xff08;棧&#xff09; 2.方法講解 2.1.解法 利用…

2023 年 亞太賽 APMCM (B題)國際大學生數學建模挑戰賽 |數學建模完整代碼+建模過程全解全析

當大家面臨著復雜的數學建模問題時&#xff0c;你是否曾經感到茫然無措&#xff1f;作為2022年美國大學生數學建模比賽的O獎得主&#xff0c;我為大家提供了一套優秀的解題思路&#xff0c;讓你輕松應對各種難題。 問題一&#xff1a; 建立沒有作物的玻璃溫室內的溫度和風速分…

C語言二十四彈--喝汽水問題

C語言解決喝汽水問題 題目&#xff1a;喝汽水&#xff0c;1瓶汽水1元&#xff0c;2個空瓶可以換一瓶汽水&#xff0c;給20元&#xff0c;可以喝多少汽水&#xff1f; 方法一、逐瓶購買法 思路&#xff1a;一瓶瓶的買 當空瓶有兩個時&#xff0c;汽水數加1即可。 #include &…

MacOS 成為惡意軟件活動的目標

Malwarebytes 警告稱&#xff0c;一個針對 Mac 操作系統 (OS) 的數據竊取程序正在通過虛假的網絡瀏覽器更新分發給毫無戒心的目標。 Atomic Stealer&#xff0c;也稱為 AMOS&#xff0c;是 Mac OS 上流行的竊取程序。 Atomic Stealer (AMOS) 惡意軟件最近被發現使用“ClearFa…