RT-Thread 线程间同步 互斥量

2024-01-10 11:46:38

互斥量

互斥量又叫互相排斥的量,是一种特殊的二值信号量。互斥量类似于只有一个车位的停车场:当有一辆车进入的时候,将停车场大门锁住,其他车辆在外面等候。当里面的车出来时,将停车场大门打开,下一辆车才可以进入。

互斥量工作机制

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

互斥量的状态只有两种,开锁或闭锁(两种状态值)。当有线程持有它时,互斥量处于闭锁状态,由这个线程获得它的所有权。相反,当这个线程释放它时,将对互斥量进行开锁,失去它的所有权。

当一个线程持有互斥量时,其它线程将不能够对它进行开锁或持有它,持有该互斥量的线程也能够再次获得这个锁而不被挂起,如下图所示。
在这里插入图片描述
这个特征与一般的二值信号量有很大的不同:在信号量中,因为已经不存在实例,线程递归持有会发生主动挂起(最终形成死锁)。

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

如下图所示:有优先级为 A、B 和 C 的三个线程,优先级 A> B > C。线程 A,B 处于挂起状态,等待某一事件触发,线程 C 正在运行,此时线程 C 开始使用某一共享资源 M。在使用过程中,线程 A 等待的事件到来,线程 A 转为就绪态,因为它比线程 C 优先级高,所以立即执行。但是当线程 A 要使用共享资源 M 时,由于其正在被线程 C 使用,因此线程 A 被挂起切换到线程 C 运行。如果此时线程 B 等待的事件到来,则线程 B 转为就绪态。由于线程 B 的优先级比线程 C 高,且线程B没有用到共享资源 M ,因此线程 B 开始运行,直到其运行完毕,线程 C 才开始运行。只有当线程 C 释放共享资源 M 后,线程 A 才得以执行。在这种情况下,优先级发生了翻转:线程 B 先于线程 A 运行。这样便不能保证高优先级线程的响应时间。

在这里插入图片描述
互斥量可以解决优先级翻转问题,实现的是优先级继承协议(Sha,1990)。优先级继承是通过在线程A尝试获取共享资源而被挂起的期间内,将线程C的优先级提升到A的优先级级别,从而解决优先级翻转引起的问题。
在这里插入图片描述
这样能够防止C(间接地防止A)被B抢占。
优先级继承是指,提高某个占有某种资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的哪个线程的优先级相等,然后执行,而当个低优先级线程释放该资源时,优先级重新回到初始设定。因此,继承优先级的线程避免了系统资源被任何中间优先级的线程抢占。

在获得互斥量后,尽快释放互斥量,并且在持有互斥量的过程中,不得再行更改持有互斥量线程的优先级,否则可能人为引入无界优先级翻转的问题。

互斥量控制块

互斥量控制块是操作系统用于管理互斥量的一个数据结构,由结构体struct rt_mutex表示。
另外一种C表达方式rt_mutex_t,表示的是互斥量的句柄,在C语言中的实现是指互斥量控制块的指针。

struct rt_mutex
{
	struct rt_ipc_object parent;
	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_mutex对象从rt_ipc_object中派生,由IPC容器所管理。

创建和删除互斥量

创建一个互斥量时,内核首先创建一个互斥量控制块,然后完成对该控制块的初始化工作。

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

系统先从对象管理器中分配一个mutex对象,并初始化这个对象,然后初始化父类IPC对象以及与mutex相关的部分。互斥量的 flag 标志已经作废,无论用户选择 RT_IPC_FLAG_PRIO 还是 RT_IPC_FLAG_FIFO,内核均按照 RT_IPC_FLAG_PRIO 处理。

获取互斥量

线程获取了互斥量,那么线程就有了对该互斥量的所有权,即某一个时刻一个互斥量只能被一个线程持有。

rt_err_t rt_mutex_take (rt_mutex_t mutex, rt_int32_t time);

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

无等待获取信号量

当用户不想再申请的互斥量上挂起线程进行等待时,可以使用无等待方式获取信号量。

rt_err_t rt_mutex_trytake(rt_mutex_t mutex);

这个函数与 rt_mutex_take(mutex, RT_WAITING_NO) 的作用相同,即当线程申请的互斥量资源实例不可用的时候,它不会等待在该互斥量上,而是直接返回 - RT_ETIMEOUT。

释放信号量

rt_err_t rt_mutex_release(rt_mutex_t mutex);

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

互斥量的使用场合

需要切记的是互斥量不能在中断服务例程中使用。

互斥量的使用比较单一,因为它是信号量的一种,并且它是以锁的形式存在。
在初始化时,互斥量永远都是开锁状态,而被线程持有的时候则立刻转为闭锁的状态。互斥量更适合于:

  1. 线程多次持有互斥量的情况下。这样可以避免同一线程多次递归持有而造成死锁的问题。
  2. 可能会由于多线程同步而造成优先级翻转的情况。

文章来源:https://blog.csdn.net/Caramel_biscuit/article/details/135495771
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。