RT-Thread學習筆記(四)

RT-Thread學習筆記

  • 線程間同步
    • 信號量
    • 信號量的使用和管理
    • 動態創建信號量
    • 靜態創建信號量
    • 獲取信號量
    • 信號量同步實列
    • 互斥量
    • 互斥量的使用和管理
    • 互斥量動態創建
    • 互斥量靜態創建
    • 互斥量獲取和釋放
    • 互斥量實例
    • 事件集
    • 事件集的使用和管理
    • 動態創建事件集
    • 靜態初始化事件集
    • 發送和接收事件
    • 事件集實現線程同步實例

線程間同步

線程間同步的作用:多個執行單元(線程、中斷)同時執行臨界區,操作臨界資源,會導致竟態產生,線程間同步確保多個線程在訪問共享資源時,能夠按照正確的順序執行,避免數據沖突和邏輯錯誤。

臨界區(Critical Section)是指一個程序中訪問共享資源(如全局變量、內存、文件、硬件設備)的一段代碼區域,在這段代碼執行過程中,必須保證同一時刻最多只能有一個線程進入執行,以避免數據競爭和不一致問題。

? 具體作用如下:
防止數據競爭(競態條件)
多個線程同時修改同一份數據時,如果沒有同步機制,就可能出現數據錯誤或程序行為異常。
保證數據一致性
同步機制(如互斥鎖、信號量等)可以確保同一時間只有一個線程訪問臨界資源,保證操作的原子性。
協調線程執行順序
某些任務必須在另一個線程完成之后才能執行,比如:先讀取文件,后處理數據。這種依賴關系就需要同步手段來控制執行順序。
提高程序穩定性與可靠性
通過同步機制可以避免死鎖、資源沖突等常見問題,使并發程序更健壯。

🔧RT-Thread OS提供了如下幾種同步互斥機制:
互斥鎖(mutex)
信號量(semaphore)
事件集(event)

信號量

信號量是一種輕型的用于解決線程間同步問題的內核對象,即信號量也是通過結構體定義描述的,線程可以獲取釋放它,從而達到同步互斥的目的。每個信號量對象都有一個信號量值和一個線程等待隊列,信號量的值對應了信號量對象的實例數目、資源數目,假如信號量值為 5,則表示共有 5 個信號量實例(資源)可以被使用,當信號量實例數目為零時,再申請該信號量的線程就會被掛起在該信號量的等待隊列上,等待可用的信號量實例(資源)

例如初始化信號量值為 1 ,當線程執行到臨界區時要獲取信號量,此時信號量值為 1,因此該線程可以獲取到信號量,當信號量被獲取一次后,信號量值會 減1,此時信號量值為0,當下一個線程需要獲取信號量時,該線程就會被掛起,放到一個等待隊列中去,等到信號量資源大于0時,回到對應的等待隊列把掛起的線程喚醒,即將線程從掛起態切換為就緒態

信號量結構體
該結構體里有信號量的value值,還有一個預留參數,為后期的擴展使用

struct rt_semaphore
{struct rt_ipc_object parent;                        /**< inherit from ipc_object */rt_uint16_t          value;                         /**< value of semaphore. */rt_uint16_t          reserved;                      /**< reserved field 預留*/
};
typedef struct rt_semaphore *rt_sem_t;

信號量的使用和管理

對一個信號量的操作包含:創建 / 初始化信號量、獲取信號量、釋放信號量、刪除 / 脫離信號量

首先要創建或初始化一個信號量,線程在臨界區前獲取信號量,在臨界區后釋放信號量

在這里插入圖片描述

動態創建信號量

動態創建信號量函數

第一個參數是信號量的名字,第二個是信號量值,

rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */

? 1. RT_IPC_FLAG_FIFO(先進先出)
含義:按線程進入等待隊列的先后順序來喚醒(排隊公平)。
特點:誰先來誰先走,無視線程優先級。
應用場景:對響應時間要求不高,強調公平性,例如:簡單同步、非實時任務。
🧠 示例: 線程 A、B、C 優先級不同,但如果是 A 先等待信號量,那釋放信號量時 A 會先被喚醒,即使 B 優先級更高。

? 2. RT_IPC_FLAG_PRIO(優先級優先)
含義:按線程的優先級高低來決定喚醒順序。
特點:優先級高的線程先被喚醒。
應用場景:對實時性要求高的系統,優先響應高優先級任務。
🧠 示例: 線程 A(低優先級)先等待資源,線程 B(高優先級)后加入等待隊列;當資源釋放時,線程 B 會被優先喚醒。

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

刪除信號量函數

rt_err_t rt_sem_delete(rt_sem_t sem)

創建演示

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_sem_t sem1;int main(void)
{sem1 = rt_sem_create("sem1_demo", 1, RT_IPC_FLAG_FIFO);//先進先出if(sem1 == RT_NULL)//如果創建失敗{LOG_E("rt_sem_create failed...\n");return -ENOMEM;}LOG_D("rt_sem_create successed...\n");//如果創建成功return RT_EOK;
}

信號量創建成功,但是信號量的名字不是sem1_demo,少了一個字符,因為信號量名字最多8個字符

在這里插入圖片描述

改短一點就可以了

在這里插入圖片描述

靜態創建信號量

初始化信號量

第一個參數是rt_sem_t 類型的結構體指針,需要定義,第二個參數是信號量的名字,第三個參數是信號量值,第四個參數是flag,與動態創建一樣

rt_err_t rt_sem_init(rt_sem_t    sem,const char *name,rt_uint32_t value,rt_uint8_t  flag)

信號量脫離

脫離信號量就是讓信號量對象從內核對象管理器中脫離,適用于靜態初始化的信號量使用該函數后,內核先喚醒所有掛在該信號量等待隊列上的線程,然后將該信號量從內核對象管理器中脫離。原來掛起在信號量上的等待線程將獲得 - RT_ERROR 的返回值。

rt_err_t rt_sem_detach(rt_sem_t sem)

初始化演示

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_sem_t sem1;struct rt_semaphore sem2;//定義一個信號量結構體用于初始化信號量int main(void)
{int ret = 0;//用于接收初始化的返回值sem1 = rt_sem_create("sem1", 1, RT_IPC_FLAG_FIFO);//先進先出if(sem1 == RT_NULL)//如果創建失敗{LOG_E("rt_sem_create failed...\n");return -ENOMEM;}LOG_D("rt_sem_create successed...\n");//如果創建成功ret = rt_sem_init(&sem2, "sem2", 5, RT_IPC_FLAG_FIFO);if(ret < 0)//如果初始化失敗{LOG_E("rt_sem_init failed...\n");return ret;}LOG_D("rt_sem_init successed...\n");//如果初始化成功return RT_EOK;
}

在這里插入圖片描述

獲取信號量

假設有一個臨界資源是讓 i++,如果兩個線程都可以操作該臨界資源,即兩個線程都會讓 i++,這時候就會發生競爭,因此就需要使用信號量的獲取和釋放

獲取信號量函數

第一個參數是要獲取哪個信號量,第二個參數timeout決定了是否阻塞等待,RT_WAITING_FOREVER表示一直等待,直到有信號量,T_WAITING_NO表示不等待,直接返回

rt_err_t rt_sem_take(rt_sem_t sem, rt_int32_t timeout)#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

另一個獲取信號量的函數,等價于rt_sem_take(rt_sem_t sem, T_WAITING_NO),即不等待,直接返回

rt_err_t rt_sem_trytake(rt_sem_t sem)

釋放信號量函數

在操作完臨界資源后要去釋放信號量,不然其他線程無法獲取這個信號量

rt_err_t rt_sem_trytake(rt_sem_t sem)

信號量同步實列

信號量的同步是指通過信號量(semaphore)來協調多個線程或任務之間的執行時機,使它們按照正確的順序運行,從而實現“誰先做、誰后做”的同步控制關系。

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_sem_t sem1;rt_thread_t th1,th2;int flag = 0;struct rt_semaphore sem2;//定義一個信號量結構體用于初始化信號量void th1_entry(void *parameter)//線程1入口函數
{while(1)//循環執行{rt_thread_mdelay(5000);//一開始處于阻塞狀態,會跳轉到線程2rt_sem_take(sem1, RT_WAITING_FOREVER );flag ++;if(flag == 100)flag = 0;rt_kprintf("th1_entry[%d]\n",flag);rt_sem_release(&sem2);}
}void th2_entry(void *parameter)//線程2入口函數
{while(1)//一開始跳轉到線程2不會立即執行線程2,因為信號量2的值為0,因此處于掛起狀態{rt_sem_take(&sem2, RT_WAITING_FOREVER);if(flag > 0)flag --;rt_kprintf("th2_entry[%d]\n",flag);rt_sem_release(sem1);rt_thread_mdelay(1000);}
}int main(void)
{int ret = 0;//用于接收初始化的返回值sem1 = rt_sem_create("sem1", 1, RT_IPC_FLAG_FIFO);//初始化信號量1的值為1if(sem1 == RT_NULL)//如果創建失敗{LOG_E("rt_sem_create failed...\n");return -ENOMEM;}LOG_D("rt_sem_create successed...\n");//如果創建成功ret = rt_sem_init(&sem2, "sem2", 0, RT_IPC_FLAG_FIFO);//初始化信號量2的值為0if(ret < 0)//如果初始化失敗{LOG_E("rt_sem_init failed...\n");return ret;}LOG_D("rt_sem_init successed...\n");//如果初始化成功th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);return RT_EOK;
}

分別創建兩個線程,flag作為臨界區,兩個線程都會對flag進行操作,因此需要通過信號量防止競態的發生,在初始化時,信號量1的值為1,信號量2的值為2,線程th1獲取的是信號量1,釋放的是信號量2,線程2獲取的是信號量2,釋放的是信號量1

在一開始,線程1的入口函數先用一個延時阻塞,這時線程1被掛起,線程2被調度,但是此時的信號量2值為0,線程2獲取不到信號量,因此線程2被掛起,等到線程1的延時結束,又回到線程1,此時線程1獲取信號量1成功,執行相應的操作,然后釋放信號量2,信號量1的值從1變為0,信號量2的值從0變為1,線程1執行完,又輪到線程2執行,此時信號量2值為1,因此線程2能夠獲取信號量2,對應的操作能夠執行,線程2又釋放信號量1,此時信號量1的值又變為1,信號量2的值又變為0,依次循環,就實現了信號的同步操作

請添加圖片描述

互斥量

互斥量體現的是排他性,也是解決多線程同時操作臨界區臨界資源導致的竟態的一種方法。(類似于特殊的信號量——二值信號量(只有 01 )),即一個線程在執行時,另一個線程不能執行,不強調順序

與信號量的區別:信號量可由不同線程釋放,互斥量只能由同一線程進行釋放。

互斥量的使用和管理

互斥量的操作包含:創建 / 初始化互斥量、獲取互斥量、釋放互斥量、刪除 / 脫離互斥量

在這里插入圖片描述

互斥量動態創建

不再使用互斥量時,通過刪除互斥量以釋放系統資源,適用于動態創建的互斥量當刪除一個互斥量時,所有等待此互斥量的線程都將被喚醒,等待線程獲得的返回值是 - RT_ERROR

互斥量動態創建函數

rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */

互斥量刪除函數

rt_err_t rt_mutex_delete(rt_mutex_t mutex)
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;int main(void)
{mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");return RT_EOK;
}

在這里插入圖片描述

互斥量靜態創建

使用該函數接口后,內核先喚醒所有掛在該互斥量上的線程(線程的返回值是 -RT_ERROR ) ,然后系統將該互斥量從內核對象管理器中脫離。

互斥量靜態創建函數

第一個參數也是要先定義一個結構體作為參數傳入

rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)

互斥量脫離函數

rt_err_t rt_mutex_detach(rt_mutex_t mutex)
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;struct rt_mutex mutex2;//定義一個結構體用于靜態創建互斥量傳參int main(void)
{int ret;//用于接收靜態創建的返回值//動態創建mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");//靜態創建ret = rt_mutex_init(&mutex2, "mutex2", RT_IPC_FLAG_FIFO);if(ret < 0){LOG_E("mutex2 rt_mutex_init failed...\n");return ret;}LOG_D("mutex2 rt_mutex_init successed...\n");return RT_EOK;
}

在這里插入圖片描述

互斥量獲取和釋放

互斥量獲取函數

rt_err_t rt_mutex_take(rt_mutex_t mutex, rt_int32_t timeout)#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

另一種獲取函數,等價于rt_mutex_take(rt_mutex_t mutex,RT_WAITING_NO)

rt_err_t rt_mutex_trytake(rt_mutex_t mutex)

互斥量釋放函數

rt_err_t rt_mutex_release(rt_mutex_t mutex)

獲取相當于上鎖,釋放相當于解鎖,上了鎖的要等到解鎖后才能使用,例如上廁所,有一個人進去了,上了鎖,下一個人要使用這個廁所的話一定要等到上一個人解鎖出來了,下一個人才能進入廁所,上鎖,且沒有順序,誰先來就誰去

上面的內容都是創建或初始化,沒有調用過刪除或這脫離,在這里調用一下,看看效果是怎么樣的,但是一般情況下一個線程或者信號量或互斥量創建之后,就會一直使用,因此不需要去刪除,但有時也會存在特殊情況

在這里插入圖片描述

在這里插入圖片描述

互斥量實例

沒有互斥量

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;rt_thread_t th1,th2;int flag1 ,flag2;void th1_entry(void *parameter)//線程1入口函數
{while(1)//循環執行{flag1 ++;rt_thread_mdelay(1000);}
}void th2_entry(void *parameter)//線程2入口函數
{while(1){flag1 ++;flag2 ++;    rt_kprintf("flag1:[%d] flag2:[%d]\n",flag1,flag2);rt_thread_mdelay(1000);}
}int main(void)
{//動態創建mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);return RT_EOK;
}

上面的代碼定義了兩個變量,flag1和flag2作為臨界區,代碼原本的功能是讓flag1和flag2同步增加,應該兩個值是相等的,創建兩個線程同時操作這兩個變量,在沒有互斥量的情況下,會出現不同步和覆蓋的可能,導致兩個變量不一致、不連續,如下圖結果,flag1 != flag2,因此要加上互斥量進行保護

請添加圖片描述

加上互斥量

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_mutex_t mutex1;rt_thread_t th1,th2;int flag1 ,flag2;void th1_entry(void *parameter)//線程1入口函數
{while(1)//循環執行{rt_mutex_take(mutex1, RT_WAITING_FOREVER);//上鎖flag1 ++;rt_thread_mdelay(1000);flag2 ++;rt_mutex_release(mutex1);//解鎖}
}void th2_entry(void *parameter)//線程2入口函數
{while(1){rt_mutex_take(mutex1, RT_WAITING_FOREVER);//上鎖flag1 ++;flag2 ++;rt_mutex_release(mutex1);//解鎖rt_kprintf("flag1:[%d] flag2:[%d]\n",flag1,flag2);rt_thread_mdelay(1000);}
}int main(void)
{//動態創建mutex1 = rt_mutex_create("mutex1", RT_IPC_FLAG_FIFO);if(mutex1 == RT_NULL){LOG_E("mutex1 rt_mutex_create failed...\n");return -ENOMEM;}LOG_D("mutex1 rt_mutex_create successed...\n");th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);return RT_EOK;
}

加上了互斥量對臨界區的保護,同一時間,只有一個線程能夠對臨界區進行操作,操作完成后解鎖了,下一個線程才能對臨界區進行操作,這樣兩個變量就能夠同步相等了

請添加圖片描述

事件集

事件集也是線程間同步的機制之一,一個事件集可以包含多個事件,利用事件集可以完成一對多,多對多的線程間同步。

一個線程和多個事件的關系可設置為: 其中任意一個事件喚醒 線程,或幾個事件都到達后喚醒線程,多個事件集合可以用一個32bit無符號整型變量來表示,變量的每一位代表一個事件,線程通過"邏輯與"或"邏輯或"將一個或多個事件關聯起來,形成事件組合

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

  1. 事件只與線程相關,事件間相互獨立
  2. 事件僅用于同步,不提供數據傳輸功能
  3. 事件無排隊性,即多次向線程發送同一事件(如果線程還未來得及讀走),其效果等同于只發送一次

在這里插入圖片描述

事件集的使用和管理

對一個事件集的操作包含:創建/初始化事件集、發送事件、接收事件、刪除/脫離事件集

在這里插入圖片描述

動態創建事件集

動態創建事件集函數

函數的返回值是一個rt_event_t數據類型的結構體,其中結構體中有一個rt_uint32_t數據類型的 set 集合,這個集合里面每一位代表一個事件,因此一個事件集里最多支持32個事件

rt_event_t rt_event_create(const char *name, rt_uint8_t flag)#define RT_IPC_FLAG_FIFO                0x00            /**< FIFOed IPC. @ref IPC. */
#define RT_IPC_FLAG_PRIO                0x01            /**< PRIOed IPC. @ref IPC. */struct rt_event
{struct rt_ipc_object parent;                        /**< inherit from ipc_object */rt_uint32_t          set;                           /**< event set */
};
typedef struct rt_event *rt_event_t;

刪除事件集函數

rt_err_t rt_event_delete(rt_event_t event)

靜態初始化事件集

靜態初始化事件集函數

要創建一個rt_event_t類型的結構體指針作為參數傳入

rt_err_t rt_event_init(rt_event_t event, const char *name, rt_uint8_t flag)

事件集脫離函數

rt_err_t rt_event_detach(rt_event_t event)
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_event_t event1;struct rt_event event2;int main(void)
{int ret;//動態創建事件集event1 = rt_event_create("event1", RT_IPC_FLAG_FIFO);if(event1 == RT_NULL)//如果創建失敗{LOG_E("event1 rt_event_create failed...\n");return -ENOMEM;}LOG_D("event1 rt_event_create successed...\n");//如果創建成功//靜態初始化事件集ret = rt_event_init(&event2, "event2", RT_IPC_FLAG_FIFO);if(ret < 0)//如果初始化失敗{LOG_E("event2 rt_event_init failed...\n");return ret;}LOG_D("event2 rt_event_init successed...\n");//如果初始化成功return RT_EOK;
}

在這里插入圖片描述

發送和接收事件

發送事件函數

第一個參數是事件集,第二個參數是事件集中的哪一個事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);

接收事件函數

第一個參數是事件集,第二個參數是事件集中的哪一個事件,第三個參數是與還是或還是清除了,即接收完這個位后要不要進行清除,第四個參數是超時時間,第五個參數用于存放接收到的值

rt_err_t rt_event_recv(rt_event_t   event,rt_uint32_t  set,rt_uint8_t   opt,rt_int32_t   timeout,rt_uint32_t *recved);
//opt參數                       
#define RT_EVENT_FLAG_AND               0x01            /**< logic and */
#define RT_EVENT_FLAG_OR                0x02            /**< logic or */
#define RT_EVENT_FLAG_CLEAR             0x04            /**< clear flag *///timeout參數
#define RT_WAITING_FOREVER              -1              /**< Block forever until get resource. */
#define RT_WAITING_NO                   0               /**< Non-block. */

事件集實現線程同步實例

#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>
#include "errno.h"rt_event_t event1;rt_thread_t th1,th2,th3;//分別用一個位來表示事件集中的一個事件
#define EVENT_FLAG_1 (0x1 << 0)//事件1
#define EVENT_FLAG_2 (0x1 << 1)//事件2
#define EVENT_FLAG_3 (0x1 << 2)//事件3void th1_entry(void *parameter)//線程1入口函數
{while(1)//循環執行{rt_event_recv(event1, EVENT_FLAG_1, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_WAITING_FOREVER, NULL);//接收事件1rt_kprintf("th1_entry...\n");rt_event_send(event1, EVENT_FLAG_2);//發送事件2rt_thread_mdelay(1000);}
}void th2_entry(void *parameter)//線程2入口函數
{while(1){rt_event_recv(event1, EVENT_FLAG_2, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_WAITING_FOREVER, NULL);//接收事件2rt_kprintf("th2_entry...\n");rt_event_send(event1, EVENT_FLAG_3);//發送事件3rt_thread_mdelay(1000);}
}void th3_entry(void *parameter)//線程3入口函數
{while(1){rt_event_recv(event1, EVENT_FLAG_3, RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND, RT_WAITING_FOREVER, NULL);//接收事件3rt_kprintf("th3_entry...\n");rt_event_send(event1, EVENT_FLAG_1);//發送事件1rt_thread_mdelay(1000);}
}int main(void)
{//動態創建事件集event1 = rt_event_create("event1", RT_IPC_FLAG_FIFO);if(event1 == RT_NULL){LOG_E("event1 rt_event_create failed...\n");return -ENOMEM;}LOG_D("event1 rt_event_create successed...\n");th1 = rt_thread_create("th1", th1_entry, NULL, 512, 20, 5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th1 rt_thread_create successed...\n");th2 = rt_thread_create("th2", th2_entry, NULL, 512, 20, 5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th2 rt_thread_create successed...\n");th3 = rt_thread_create("th3", th3_entry, NULL, 512, 20, 5);if(th3 == RT_NULL){LOG_E("th3 rt_thread_create failed...\n");return -ENOMEM;}LOG_D("th3 rt_thread_create successed...\n");rt_thread_startup(th1);rt_thread_startup(th2);rt_thread_startup(th3);rt_event_send(event1, EVENT_FLAG_1);//先發送一次事件1return RT_EOK;
}

在這里插入圖片描述

可以看到確實是按照線程 1、2、3 順序執行的

請添加圖片描述

創建一個臨界區,觀察是否能起到保護臨界區的作用

在這里插入圖片描述

可以看到無論每個線程的延時函數放在哪里,每個線程都是按順序執行的,對臨界區起到了保護,避免了競態現象的發生

請添加圖片描述

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

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

相關文章

element ui el-col的高度不一致導致換行

問題&#xff1a;ell-col的高度不一致導致換行&#xff0c;刷新后審查el-col的高度一致 我這邊是el-col寫的span超過了24&#xff0c;自行換行&#xff0c;測試發現初次進入里面的高度渲染的不一致&#xff0c;有的是51px有的是51.5px 問題原因分析 Flex布局換行機制 Elemen…

現代化Android開發:Compose提示信息的最佳封裝方案

在 Android 開發中&#xff0c;良好的用戶反饋機制至關重要。Jetpack Compose 提供了現代化的 UI 構建方式&#xff0c;但提示信息(Toast/Snackbar)的管理往往顯得分散。本文將介紹如何優雅地封裝提示信息&#xff0c;提升代碼可維護性。 一、基礎封裝方案 1. 簡單 Snackbar …

【C++語法】類和對象(2)

4.類和對象&#xff08;2&#xff09; 文章目錄 4.類和對象&#xff08;2&#xff09;類的六個默認成員函數(1)構造函數&#xff1a;構造函數特點含有缺省參數的構造函數構造函數特點&#xff08;續&#xff09;注意事項構造函數補充 前面總結了有關對象概念&#xff0c;對比 C…

【自然語言處理與大模型】vLLM部署本地大模型②

舉例上一篇文章已經過去了幾個月&#xff0c;大模型領域風云變幻&#xff0c;之前的vLLM安裝稍有過時&#xff0c;這里補充一個快速安裝教程&#xff1a; # 第一步&#xff1a;創建虛擬環境并激活進入 conda create -n vllm-0.8.4 python3.10 -y conda activate vllm-0…

26 Arcgis軟件常用工具有哪些

一、畫圖改圖工具&#xff08;矢量編輯&#xff09;? ?挪位置工具&#xff08;移動工具&#xff09;? 干哈的&#xff1f;?選中要素?&#xff08;比如地塊、道路&#xff09;直接拖到新位置&#xff0c;或者用坐標?X/Y偏移?批量移動&#xff0c;適合“整體搬家”。 ?磁…

QNX/LINUX/Android系統動態配置動態庫.so文件日志打印級別的方法

背景 通常我們會在量產的產品上&#xff0c;配置軟件僅打印少量日志&#xff0c;以提升產品的運行性能。同時我們要考慮預留方法讓軟件能夠擁有能力可以在燒錄版本后能夠通過修改默寫配置&#xff0c;打印更多日志。因為量產后的軟件通常開啟熔斷與加密&#xff0c;不能夠輕松…

WebGL圖形編程實戰【4】:光影交織 × 逐片元光照與渲染技巧

現實世界中的物體被光線照射時&#xff0c;會反射一部分光。只有當反射光線進人你的眼睛時&#xff0c;你才能夠看到物體并辯認出它的顏色。 光源類型 平行光&#xff08;Directional Light&#xff09;&#xff1a;光線是相互平行的&#xff0c;平行光具有方向。平行光可以看…

【Hive入門】Hive基礎操作與SQL語法:DDL操作全面指南

目錄 1 Hive DDL操作概述 2 數據庫操作全流程 2.1 創建數據庫 2.2 查看數據庫 2.3 使用數據庫 2.4 修改數據庫 2.5 刪除數據庫 3 表操作全流程 3.1 創建表 3.2 查看表信息 3.3 修改表 3.4 刪除表 4 分區與分桶操作 4.1 分區操作流程 4.2 分桶操作 5 最佳實踐與…

YOLO數據處理

YOLO&#xff08;You Only Look Once&#xff09;的數據處理流程是為了解決目標檢測領域的核心挑戰&#xff0c;核心目標是為模型訓練和推理提供高效、規范化的數據輸入。其設計方法系統性地解決了以下關鍵問題&#xff0c;并對應發展了成熟的技術方案&#xff1a; 一、解決的問…

Ubuntu-Linux中vi / vim編輯文件,保存并退出

1.打開文件 vi / vim 文件名&#xff08;例&#xff1a; vim word.txt &#xff09;。 若權限不夠&#xff0c;則在前方添加 sudo &#xff08;例&#xff1a;sudo vim word.txt &#xff09;來增加權限&#xff1b; 2.進入文件&#xff0c;按 i 鍵進入編輯模式。 3.編輯結…

PCL繪制點云+法線

讀取的點云ASCII碼文件&#xff0c;每行6個數據&#xff0c;3維坐標3維法向 #include <iostream> #include <fstream> #include <vector> #include <string> #include <pcl/point_types.h> #include <pcl/point_cloud.h> #include <pc…

如何在學習通快速輸入答案(網頁版),其他學習平臺通用,手機上快速粘貼

目錄 1、網頁版&#xff08;全平臺通用&#xff09; 2、手機版&#xff08;學習通&#xff0c;其他平臺有可能使用&#xff09; 1、網頁版&#xff08;全平臺通用&#xff09; 1、首先CtrlC復制好答案 2、在學習通的作業里輸入1 3、對準1&#xff0c;然后鼠標右鍵 &#xff…

002 六自由度舵機機械臂——姿態解算理論

00 DH模型的核心概念 【全程干貨【六軸機械臂正逆解計算及仿真示例】】 如何實現機械臂的逆解計算-機器譜-robotway DH模型是機器人運動學建模的基礎方法&#xff0c;通過??四個參數??描述相鄰關節坐標系之間的變換關系。其核心思想是將復雜的空間位姿轉換分解為繞軸旋轉…

pymongo功能整理與基礎操作類

以下是 Python 與 PyMongo 的完整功能整理&#xff0c;涵蓋基礎操作、高級功能、性能優化及常見應用場景&#xff1a; 1. 安裝與連接 (1) 安裝 PyMongo pip install pymongo(2) 連接 MongoDB from pymongo import MongoClient# 基礎連接&#xff08;默認本地&#xff0c;端口…

Trae+DeepSeek學習Python開發MVC框架程序筆記(四):使用sqlite存儲查詢并驗證用戶名和密碼

繼續通過Trae向DeepSeek發問并修改程序&#xff0c;實現程序運行時生成數據庫&#xff0c;用戶在系統登錄頁面輸入用戶名和密碼后&#xff0c;控制器通過模型查詢用戶數據庫表來驗證用戶名和密碼&#xff0c;驗證通過后顯示登錄成功頁面&#xff0c;驗證失敗則顯示登錄失敗頁面…

如何識別金融欺詐行為并進行分析預警

金融行業以其高效便捷的服務深刻改變了人們的生活方式。然而,伴隨技術進步而來的,是金融欺詐行為的日益猖獗。從信用卡盜刷到復雜的龐氏騙局,再到網絡釣魚和洗錢活動,金融欺詐的形式層出不窮,其規模和影響也在不斷擴大。根據全球反欺詐組織(ACFE)的最新報告,僅2022年,…

紛析云:開源財務管理軟件的創新與價值

在企業數字化轉型中&#xff0c;紛析云作為一款優秀的開源財務管理軟件&#xff0c;正為企業財務管理帶來新變革&#xff0c;以下是其核心要點。 一、產品概述與技術架構 紛析云采用微服務架構&#xff0c;功能組件高內聚低耦合&#xff0c;可靈活擴展和定制。前端基于現代框…

蛋白質大語言模型ESM介紹

ESM(Evolutionary Scale Modeling)是 Meta AI Research 團隊開發的一系列用于蛋白質的預訓練語言模型。這些模型在蛋白質結構預測、功能預測和蛋白質設計等領域展現出了強大的能力。以下是對 ESM 的詳細介紹: 核心特點 大規模預訓練:基于大規模蛋白質序列數據進行無監督學…

OpenCv高階(七)——圖像拼接

目錄 一、圖像拼接的原理過程 1. 特征檢測與描述&#xff08;Feature Detection & Description&#xff09; 2. 特征匹配&#xff08;Feature Matching&#xff09; 3. 圖像配準&#xff08;Image Registration&#xff09; 4. 圖像變換與投影&#xff08;Warping&…

Native層Trace監控性能

一、基礎實現方法 1.1 頭文件引用 #include <utils/Trace.h> // 基礎版本 #include <cutils/trace.h> // 兼容舊版本1.2 核心宏定義 // 區間追蹤&#xff08;推薦&#xff09; ATRACE_BEGIN("TraceTag"); ...被監控代碼... ATRACE_END();// 函數級自…