目录
线程间同步
信号量
信号量结构体
信号量的使用和管理
动态创建信号量
实例
静态创建信号量
初始化和脱离信号量
获取信号量
信号量的互斥操作
获取信号量函数
释放信号量
信号量同步实例
互斥量(互斥锁)
互斥量的使用和管理
动态创建互斥量
动态创建过程
静态创建互斥量
静态创建过程
获取互斥量
释放互斥量
互斥锁实例
事件集
动态创建事件集
创建和删除
静态创建事件集
初始化和脱离
事件集创建过程
事件集实现线程同步
发送事件
接收事件
实现原理
实现过程
事件集实现进程同步源码
线程间同步
多个执行单元(线程、中断)同时执行临界区(多个执行单元会同时执行到的代码区域),操作临界资源,会导致竟态产生,为了解决这种竟态问题,RT-Thread OS提供了如下几种同步互斥机制:
- 信号量
- 互斥量
- 事件集
同步的概念是:多个执行单元执行临界区的时候按照顺序的方式执行
互斥的概念:互斥强调的是排他性,比如有两个线程A和B。当线程A执行的时候,线程B不能执行;线程B执行的时候,线程A不能执行。只有当一个线程执行完之后,另一个线程才能执行。
信号量
信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。
每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。
信号量结构体
当信号量被使用,value的值减1;当信号量被释放,value值加1
struct rt_semaphore
{struct rt_ipc_object parent; /**< inherit from ipc_object 继承自ipc_object类*/rt_uint16_t value; /**< value of semaphore. */rt_uint16_t reserved; /**< reservedfield 预留*/
};typedef struct
rt_semaphore *rt_sem_t;
信号量的使用和管理
对一个信号量的操作包含:创建 /初始化信号量、获取信号量、释放信号量、删除 / 脱离信号量。
创建完信号量之后,需要找到临界区,在临界区之前获取信号量,在临界区之后要释放信号量。
动态创建信号量
参数1:信号量名字
参数2:信号量资源个数
参数3:#define RT_IPC_FLAG_FIFO 0x00 //按照线程先进先出获取信号量资源
#define RT_IPC_FLAG_PRIO 0x01 //按照线程优先级获取信号量资源
返回值为信号量结构体指针
/*** This function will create a semaphore from system resource** @param name the name of semaphore* @param value the initial value of semaphore* @param flag the flag of semaphore** @return the created semaphore, RT_NULL on error happen** @see rt_sem_init*/
rt_sem_t rt_sem_create(const char *name, rt_uint32_t value, rt_uint8_t flag)
系统不再使用信号量时,可通过删除信号量以释放系统资源,适用于动态创建的信号量。
调用这个函数时,系统将删除这个信号量。如果删除该信号量时,有线程正在等待该信号量,那么删除操作会先唤醒等待在该信号量上的线程(等待线程的返回值是-RT_ERROR),然后再释放信号量的内存资源。
/*** This function will delete a semaphore objectand release the memory** @param sem the semaphore object** @return the error code** @see rt_sem_detach*/
rt_err_t rt_sem_delete(rt_sem_t sem)
实例
semaphore为信号量的名字,V为信号量的当前资源数,suspend thread为挂载在信号量上的线程数。但发现信号量名字少了最后面的“1”
注意名字设置的时候,必须要小于等于8个字符
静态创建信号量
初始化和脱离信号量
/*** This function will initialize a semaphoreand put it under control of* resource management.** @param sem the semaphore object* @param name the name of semaphore* @param value the initial value of semaphore* @param flag the flag of semaphore** @return the operation status, RT_EOK onsuccessful*/
rt_err_t rt_sem_init(rt_sem_t sem,const char *name,rt_uint32_t value,rt_uint8_t flag)
脱离信号量就是让信号量对象从内核对象管理器中脱离,适用于静态初始化的信号量,
使用该函数后,内核先唤醒所有挂在该信号量等待队列上的线程,然后将该信号量从内核对象管理器中脱离。原来挂起在信号量上的等待线程将获得 - RT_ERROR 的返回值。
/*** This function will detach a semaphore from resource management** @param sem the semaphore object** @return the operation status, RT_EOK on successful** @see rt_sem_delete*/
rt_err_t rt_sem_detach(rt_sem_t sem)
获取信号量
线程通过获取信号量来获得信号量资源实例,当信号量值大于零时,线程将获得信号量,并且相应的信号量值会减 1,如果信号量的值等于零,那么说明当前信号量资源实例不可用,申请该信号量的线程将根据time参数的情况选择直接返回、或挂起等待一段时间、或永久等待,直到其他线程或中断释放该信号量。如果在参数time指定的时间内依然得不到信号量,线程将超时返回,返回值是 - RT_ETIMEOUT。
比如说现在有线程1和线程2两个线程同时都能执行临界区的代码,在临界区存在一个临界资源变量i。此时线程1和线程2是存在竞态关系的,我们可以通过信号量来阻止竞态的发生。
我们将信号量的value设置为1,在进入临界区之前,我们先获取信号量,在出临界区的时候再释放信号量。那么不管是线程1还是线程2,谁先执行临界区,都会先获取信号量,然后信号量value减1,变成0;此时线程2发现信号量value为0,就会选择休眠。直到线程1释放掉信号量,value值加1,就会唤醒线程2。那么线程2就会获取到信号量,从而操作临界区。
信号量的互斥操作
不管是谁先进入临界区操作临界区的资源,其它的线程就没办法来操作;只有等到操作完临界区的线程释放资源,其它的线程才能进行操作;每一个时刻只有一个线程可以操作临界区。
获取信号量函数
第一个参数为信号量的结构体指针
第二个参数为time,我们通过time可以知道如果线程获取不到信号量的时候是处于什么样的状态(如果为RT_WAITING_FOREVER就进入休眠状态一直等待信号量;RT_WAITING_NO为非阻塞的模式,如果线程获取不到信号量就直接返回,不会休眠;再然后其它的time值就是自己设置的等待时间:注意时间要大于0)
释放信号量
释放信号量可以唤醒挂起在该信号量上的线程
/*** This function will release a semaphore, ifthere are threads suspended on* semaphore, it will be waked up.** @param sem the semaphore object** @return the error code*/
rt_err_t rt_sem_release(rt_sem_t sem)
信号量同步实例
(1)首先创建一个全局变量flags
(2)然后创建两个线程,让它们都能够操作到flags(我们将线程的优先级设置为相同优先级,这样才可能会导致竞态产生)
(3)此时我们想实现线程1和线程2的同步操作,即先执行线程1然后执行线程2,顺序执行
原理:比如说我们现在有两个信号量,信号量1初始化value为1,信号量2初始化value为0。线程1进入临界空间以后首先获取信号量1(信号量value大于1,获取成功),等线程1执行完之后信号量1会自动减1,然后释放信号量2,信号量2加1;此时线程2可以获取到信号量2,开始执行,执行完之后信号量2自动减1,然后释放信号量1,信号量1加1,然后线程1又开始执行了。按照顺序依次执行。
(4)创建两个信号量sem1和sem2
(5)完善线程执行函数:线程1先休眠8秒,线程2执行完一次再休眠2秒
(6)main函数中启动两个线程
(7)实验现象
烧写程序上电后,发现两个线程都处于挂起状态
因为线程1处于8秒的延时状态,而线程2此时无法获取到信号量
运行完效果:两个线程按顺序交替操作,flags的值在0-1跳转
互斥量(互斥锁)
互斥量体现的是排他性,也是解决多线程同时操作临界区临界资源导致的竟态的一种方法。(类似于特殊的信号量——二值信号量)
区别:信号量可由不同线程释放,互斥量只能由同一线程进行释放。
互斥量的使用和管理
互斥量的操作包含:创建 / 初始化互斥量、获取互斥量(相对于上锁)、释放互斥量(相对于解锁)、删除 / 脱离互斥量
动态创建互斥量
rt_mutex_create函数用于创建互斥量
参数1:信号量名字
参数2:#define RT_IPC_FLAG_FIFO 0x00 //按照线程先进先出获取信号量资源
#define RT_IPC_FLAG_PRIO 0x01 //按照线程优先级获取信号量资源
返回值为结构体指针变量rt_mutex_t
#ifdef RT_USING_HEAP
/*** This function will create a mutex from system resource** @param name the name of mutex* @param flag the flag of mutex** @return the created mutex, RT_NULL on error happen** @see rt_mutex_init*/
rt_mutex_t rt_mutex_create(const char *name, rt_uint8_t flag)
不再使用互斥量时,通过删除互斥量以释放系统资源,适用于动态创建的互斥量
当删除一个互斥量时,所有等待此互斥量的线程都将被唤醒,等待线程获得的返回值是
- RT_ERROR
/*** This function will delete a mutex object andrelease the memory** @param mutex the mutex object** @return the error code** @see rt_mutex_detach*/
rt_err_t rt_mutex_delete(rt_mutex_t mutex)
动态创建过程
静态创建互斥量
初始化和脱离互斥量
注意传入的第一个参数是结构体变量的地址
/*** This function will initialize a mutex andput it under control of resource* management.** @param mutex the mutex object* @param name the name of mutex* @param flag the flag of mutex** @return the operation status, RT_EOK onsuccessful*/
rt_err_t rt_mutex_init(rt_mutex_t mutex, const char *name, rt_uint8_t flag)
使用该函数接口后,内核先唤醒所有挂在该互斥量上的线程(线程的返回值是
-RT_ERROR) ,然后系统将该互斥量从内核对象管理器中脱离。
/*** This function will detach a mutex fromresource management** @param mutex the mutex object** @return the operation status, RT_EOK onsuccessful** @see rt_mutex_delete*/
rt_err_t rt_mutex_detach(rt_mutex_t mutex)
静态创建过程
一般创建好互斥锁,是不会去删除或者分离的,如果要删除则分别使用delete和detach
获取互斥量
/*** This function will take a mutex, if themutex is unavailable, the* thread shall wait for a specified time.** @param mutex the mutex object* @param time the waiting time** @return the error code*/
rt_err_t rt_mutex_take(rt_mutex_tmutex, rt_int32_t time)
释放互斥量
/*** This function will release a mutex, if thereare threads suspended on mutex,* it will be waked up.** @param mutex the mutex object** @return the error code*/
rt_err_t rt_mutex_release(rt_mutex_tmutex)
互斥锁实例
(1)首先创建两个临界资源flag1和flag2
(2)创建一个互斥锁
(3)创建两个线程
(4)在如下的情况下我们可以发现运行时,每次打印出来的flag1和flag2的值是不相等的
如果我们想要实现每次打印的两个值相等,则要确保线程1执行完成一次循环之后再执行线程2(即线程不会因为休眠挂起而被另一个线程操作临界区资源)。我们将线程1的while(1)设为临界区,进行上锁
(5)为相应临界区上锁和解锁
(6)结果现象
从输出的实验结果可以得知如果有两个线程使用同一把锁,则线程B必须等线程A执行完上锁和解锁及临界区的操作,才可以执行上锁操作,并继续往下执行,否则上锁失败,线程B就会进入挂起状态等待线程A解锁方可唤醒
事件集
事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。
一个线程和多个事件的关系可设置为:
其中任意一个事件唤醒 线程,或几个事件都到达后唤醒线程,多个事件集合可以用一个32bit无符号整型变量来表示,变量的每一位代表一个事件,线程通过"逻辑与"或"逻辑或"将一个或多个事件关联起来,形成事件组合。
RT-Thread 定义的事件集有以下特点:
- 事件只与线程相关,事件间相互独立
- 事件仅用于同步,不提供数据传输功能
- 事件无排队性,即多次向线程发送同一事件(如果线程还未来得及读走),其效果等同于只发送一次
动态创建事件集
创建和删除
/** event structure*/
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;/*** This function will create an event objectfrom system resource** @param name the name of event* @param flag the flag of eventRT_IPC_FLAG_FIFO RT_IPC_FLAG_PRIO** @return the created event, RT_NULL on errorhappen*/
rt_event_t rt_event_create(const char*name, rt_uint8_t flag)
/*** This function will delete an event objectand release the memory** @param event the event object** @return the error code*/
rt_err_t rt_event_delete(rt_event_tevent)
静态创建事件集
初始化和脱离
/*** This function will initialize an event andput it under control of resource* management.** @param event the event object* @param name the name of event* @param flag the flag of event** @return the operation status, RT_EOK onsuccessful*/
rt_err_t rt_event_init(rt_event_tevent, const char *name, rt_uint8_t flag)
/*** This function will detach an event objectfrom resource management** @param event the event object** @return the operation status, RT_EOK onsuccessful*/
rt_err_t rt_event_detach(rt_event_tevent)
事件集创建过程
(与前面类似,这里以动态为例)
事件集实现线程同步
事件集有多个事件,能够完成多个事件的同步。之前在完成线程同步的时候用的是信号量,但用到的是多个信号量才能完成线程的同步。而现在我们可以通过一个事件集来完成线程的同步。
事件集用的集合的类型是无符号32bit整形,事件的占位是一个事件占用一个位。所以一个集合里面支持32个事件。
发送事件
该函数给事件集对象发送一个事件,如果存在线程在事件集对象上挂起,它将会被唤醒
第一个参数为事件的结构体指针(事件集对象)
第二个参数为具体的事件值
/*** This function will send an event to the event object, if there are threads* suspended on event object, it will be wakedup.** @param event the event object* @param set the event set** @return the error code*/
rt_err_t rt_event_send(rt_event_tevent, rt_uint32_t set)
接收事件
该函数的功能是从事件集对象里面接收一个事件,如果事件是不可用的,线程将进入休眠(挂起状态)
第二个参数为指定具体接收是哪个事件
第三个参数为接收的操作,设置与和或的关系,还有是否设置在接收完某个事件后对位进行清除
第四个参数设置等待时间,与前面相同
第五个参数用于接收事件的值,如果不关心就不设置
/*** This function will receive an event from event object, if the event is* unavailable, the thread shall wait for aspecified time.** @param event the fast event object* @param set the interested event set* @param option the receive option, eitherRT_EVENT_FLAG_AND or* RT_EVENT_FLAG_OR should be set. RT_EVENT_FLAG_CLEAR * @param timeout the waiting time RT_WAITING_FOREVER RT_WAITING_NO* @param recved the received event, if youdon't care, RT_NULL can be set.** @return the error code*/
rt_err_trt_event_recv(rt_event_t event,rt_uint32_t set,rt_uint8_t option,rt_int32_t timeout,rt_uint32_t *recved)
实现原理
我们要用事件集实现三个线程的同步
如果采用信号量的话,我们需要三个信号。而采用事件集,我们只要使用三个事件位即可。
以事件2为例,当调度器调度到线程2,此时发现线程2没有接收到事件2,就无法往下执行,它就会挂起,从而线程3也无法接收到事件3。因此我们可以手动在main函数中手动发送一个事件,从而触发一个线程可以正常执行。
我们再使用事件集的时候,还可以用到or和and的关系。and:比如可以设置事件1和事件2同时满足的,线程1才可以执行;or:比如设置事件1或事件2有一个满足,线程1就可以执行
实现过程
(1)创建事件集
(3)创建三个线程
(4)发送、接收事件使用
在main中手动发送
(5)实验效果:每秒钟按照1、2、3的顺序执行
事件集实现进程同步源码
#include <rtthread.h>#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>rt_event_t event1;
rt_thread_t th1,th2,th3;#define EVENT_FLAGS_1 (0x01<<0)
#define EVENT_FLAGS_2 (0x01<<1)
#define EVENT_FLAGS_3 (0x01<<2)
void th1_entry(void *parameter)
{while(1){rt_event_recv(event1,EVNET_FLAGS_1,RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND,\RT_WAITING_FOREVER,NULL);rt_kprintf("th1_entry...\n");rt_event_send(event1,EVENT_FLAGS_2);rt_thread_mdelay(1000);}
}
void th2_entry(void *parameter)
{while(1){rt_event_recv(event1,EVNET_FLAGS_2,RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND,\RT_WAITING_FOREVER,NULL);rt_kprintf("th2_entry...\n");rt_event_send(event1,EVENT_FLAGS_3);rt_thread_mdelay(1000);}
}
void th3_entry(void *parameter)
{while(1){rt_event_recv(event1,EVNET_FLAGS_3,RT_EVENT_FLAG_CLEAR | RT_EVENT_FLAG_AND,\RT_WAITING_FOREVER,NULL);rt_kprintf("th3_entry...\n");rt_event_send(event1,EVENT_FLAGS_1);rt_thread_mdelay(1000);}
}
int main(void)
{event1 = rt_event_create("set1", RT_IPC_FLAG_FIFO);if(event1 == RT_NULL){LOG_E("rt_event_create failed...\n");return -ENOMEM;}LOG_D("rt_event_create succeed...\n");th1 = rt_thread_create("th1",th1_entry,RT_NULL,512,10,5);if(th1 == RT_NULL){LOG_E("th1 rt_thread_create failed...\n");return -ENOMEM;}th2 = rt_thread_create("th2",th2_entry,RT_NULL,512,10,5);if(th2 == RT_NULL){LOG_E("th2 rt_thread_create failed...\n");return -ENOMEM;}th3 = rt_thread_create("th3",th3_entry,RT_NULL,512,10,5);if(th3 == RT_NULL){LOG_E("th3 rt_thread_create failed...\n");return -ENOMEM;}rt_thread_startup(th1);rt_thread_startup(th3);rt_thread_startup(th2);rt_event_send(event1,EVENT_FLAGS_1);return RT_EOK;
}