RT-Thread内核学习(认真系列) -- (3)线程间同步

一、概述

 

二、信号量

信号量是一种轻型的用于解决线程间同步问题的内核对象,线程可以获取或释放它,从而达到同步或互斥的目的。

信号量工作示意图如下图所示,每个信号量对象都有一个信号量值和一个线程等待队列,信号量的值对应了信号量对象的实例数目、资源数目,假如信号量值为 5,则表示共有 5 个信号量实例(资源)可以被使用,当信号量实例数目为零时,再申请该信号量的线程就会被挂起在该信号量的等待队列上,等待可用的信号量实例(资源)。

 2.1、 API

创建和删除信号量(动态对象):

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

//删除信号量
rt_err_t rt_sem_delete(rt_sem_t sem);

初始化和脱离信号量(静态对象):

rt_err_t rt_sem_init(rt_sem_t sem,   //信号量对象指针(句柄),记得用&
                     const char *name,
                     rt_uint32_t value,
                     rt_uint8_t flag)

//脱离
rt_err_t rt_sem_detach(rt_sem_t sem);

获取信号量:

rt_err_t rt_sem_take (rt_sem_t sem, rt_int32_t time); 
        //time:指定的等待时间,单位是操作系统时钟节拍(OS Tick),
        // RT_WAITING_FOREVER 为天荒地老的等待

//无等待获取信号量
rt_err_t rt_sem_trytake(rt_sem_t sem); //等同于 rt_sem_take(sem, 0) 

释放信号量:

rt_err_t rt_sem_release(rt_sem_t sem);

2.2、 使用场合

2.2.1、线程同步

线程同步是信号量最简单的一类应用。例如,使用信号量进行两个线程之间的同步,信号量的值初始化成 0,表示具备 0 个信号量资源实例;而尝试获得该信号量的线程,将直接在这个信号量上进行等待。当持有信号量的线程完成它处理的工作时,释放这个信号量,可以把等待在这个信号量上的线程唤醒,让它执行下一部分工作。这类场合也可以看成把信号量用于工作完成标志:持有信号量的线程完成它自己的工作,然后通知等待该信号量的线程继续下一部分工作。

2.2.2、锁

对临界资源保护。

2.2.3、中断与线程中断的同步

中断来临,释放信号量通知线程(但是不要获取信号量,容易导致中断退不出来)。 FinSH 线程就是使用这种方法。

注:中断与线程间的互斥不能采用信号量(锁)的方式,而应采用开关中断的方式。

2.2.4、资源计数

信号量也可以认为是一个递增或递减的计数器,需要注意的是信号量的值非负。例如:初始化一个信号量的值为 5,则这个信号量可最大连续减少 5 次,直到计数器减为 0。资源计数适合于线程间工作处理速度不匹配的场合,这个时候信号量可以做为前一线程工作完成个数的计数,而当调度到后一线程时,它也可以以一种连续的方式一次处理多个事件。

三、互斥量

是一种特殊的二值信号量,互斥量和信号量不同的是:拥有互斥量的线程拥有互斥量的所有权,互斥量支持递归访问且能防止线程优先级翻转;并且互斥量只能由持有线程释放,而信号量则可以由任何线程释放。

支持递归访问:互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。当一个线程持有互斥量时,其他线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,如下图时所示。这个特性与一般的二值信号量有很大的不同:在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。

支持递归访问就是持有互斥量的线程再次获取该信号量就会使持有计数+1,而不会挂起,每释放一次计数-1,为0则完全释放。

防止优先级翻转:使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓优先级翻转,即当一个高优先级线
程试图通过信号量机制访问共享资源时,如果该信号量已被一低优先级线程持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,实时性难以得到保证。互斥量可以解决优先级翻转问题,实现的是优先级继承算法。

警告: 在获得互斥量后,请尽快释放互斥量,并且在持有互斥量的过程中,不得再行 更改持有互斥量线程的优先级。

3.1、API

3.1.1、创建和删除

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

3.1.2、初始化和脱离

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);

 3.1.3、获取和释放互斥量

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);
rt_err_t rt_mutex_release(rt_mutex_t mutex);

如果互斥量没有被其他线程控制,那么申请该互斥量的线程将成功获得该互斥量。如果互斥量已经被当前线程线程控制,则该互斥量的持有计数加 1,当前线程也不会挂起等待。 果互斥量已经被其他线程占有,则当前线程在该互斥量上挂起等待,直到其他线程释放它或者等待时间超过指定的超时时间。

只有已经拥有互斥量控制权的线程才能释放它,每释放一次该互斥量,它的持有计数就减 1。当该互斥量的持有计数为零时(即持有线程已经释放所有的持有操作),它变为可用,等待在该信号量上的线程将被唤醒。如果线程的运行优先级被互斥量提升,那么当互斥量被释放后,线程恢复为持有互斥量前的优先级。

3.2、应用场合

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。在初始化的时候,互斥量永远都处于开锁的状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于:
(1)线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。
(2)可能会由于多线程同步而造成优先级翻转的情况。

四、事件集 

事件集也是线程间同步的机制之一,一个事件集可以包含多个事件,利用事件集可以完成一对多,多对多的线程间同步。

事件集主要用于线程间的同步,与信号量不同,它的特点是可以实现一对多,多对多的同步。即一个线程与多个事件的关系可设置为:其中任意一个事件唤醒线程,或几个事件都到达后才唤醒线程进行后续的处理;同样,事件也可以是多个线程同步多个事件。这种多个事件的集合可以用一个 32 位无符号整型变量来表示,变量的每一位代表一个事件,线程通过 “逻辑与” 或 “逻辑或” 将一个或多个事件关联起来,形成事件组合。事件的 “逻辑或” 也称为是独立型同步,指的是线程与任何事件之一发生同步;事件 “逻辑与”也称为是关联型同步,指的是线程与若干事件都发生同步。

RT-Thread 定义的事件集有以下特点:
           1)事件只与线程相关,事件间相互独立:每个线程可拥有 32 个事件标志,采用一个 32 bit 无符号整型数进行记录,每一个 bit 代表一个事件;
           2)事件仅用于同步,不提供数据传输功能;
           3)事件无排队性,即多次向线程发送同一事件 (如果线程还未来得及读走),其效果等同于只发送一次。

每个线程都拥有一个事件信息标记,它有三个属性,分别是 RT_EVENT_FLAG_AND(逻辑与),RT_EVENT_FLAG_OR(逻辑或)以及 RT_EVENT_FLAG_CLEAR(清除标记)。当线程等待事件同步时,可以通过 32 个事件标志和这个事件信息标记来判断当前接收的事件是否满足同步条件。

4.1、API

4.1.1、创建和删除

rt_event_t rt_event_create(const char* name, rt_uint8_t flag);
//flag:事件集的标志,它可以取如下数值:RT_IPC_FLAG_FIFO 或RT_IPC_FLAG_PRIO
rt_err_t rt_event_delete(rt_event_t event);

4.1.2、初始化和脱离

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);

4.1.3、发送事件

rt_err_t rt_event_send(rt_event_t event, rt_uint32_t set);//set发送的一个或多个事件的标志值
                                                          //之间使用 | 

如果存在多个事件,事件之间用 |; 

4.1.4、接收事件

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);//指向接收到的事件

如果存在多个事件,事件之间用 |; 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值