线程的优先级翻转
优先级翻转
使用信号量会导致的另一个潜在问题是线程优先级翻转问题。所谓线程优先级翻转,即当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,如果该IPC对象已被一低优先级线程所持有,而这个低优先级线程在运行过程中可能又被其它一些中等优先级线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。优先级翻转会造成高优先级线程的实时性得不到保证。
如下图,由于低优先级的线程C获取到互斥量M,使得高优先级的线程A无法获取互斥量M,但中等优先级的线程B没有和A的互斥量,故它能够获取信号量并进行相应操作,直到B释放信号量后,C才继续运行,直到它释放互斥量M后,A才会被唤醒。
优先级继承
在RT-Thread中,通过互斥量的优先级继承算法,可以有效的解决优先级翻转问题。
优先级继承是指提高某个占有某种共享资源的低优先级线程的优先级,使之与所有等待该资源的线程中优先级最高的那个线程的优先级相等,从而得到更快的执行然后释放共享资源,而当这个低优先级线程释放该资源时,优先级重新回到初始设定值。
继承优先级的线程避免了系统共享资源被任何中间优先级的线程抢占。
如下图,由于低优先级的线程C获取到互斥量M,使得高优先级的线程A无法获取互斥量M,由于中等优先级的线程B没有和A的互斥量,它能够获取信号量并进行相应操作,但是由于优先级继承算法的存在,此时C的优先级被提升到和A的优先级相同,故B无法抢占。C释放信号量后,恢复其原来的优先级,此时由于优先级顺序,A获取到信号量并继续运行,直到它释放信号量后,B才会被唤醒。
小例
代码
/*
* 程序清单:互斥量使用例程
*
* 这个例子将创建 3 个动态线程以检查持有互斥量时,持有的线程优先级是否
* 被调整到等待线程优先级中的最高优先级。
*
* 线程 1,2,3 的优先级从高到低分别被创建,
* 线程 3 先持有互斥量,而后线程 2 试图持有互斥量,此时线程 3 的优先级应该
* 被提升为和线程 2 的优先级相同。线程 1 用于检查线程 3 的优先级是否被提升
* 为与线程 2的优先级相同。
*/
#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 */
/* 检查 rt_kprintf("the producer generates a number: %d\n", array[set%MAXSEM]); 与 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);
/* 低优先级的线程3先获取互斥量 */
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_FIFO);
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, pri_inversion sample);
结果
分析
由于线程1,线程2,线程3的优先级由低到高,且线程1和线程2的入口函数一开始就分别延时100ms,50ms使线程挂起,所以线程3首先获取互斥量并挂起,由于互斥量在线程3手上,故线程2就绪后无法获取互斥量。当线程1延时结束就绪后,由于线程1和线程3没有互斥量的影响,线程1抢占优先级,此时被挂起的优先级最高为线程2的优先级(10),故线程3的优先级被抬高,并在线程1中被打印出来,故此时线程2和线程3优先级都为10。之后,线程1运行结束,直到线程3释放互斥量后,线程2才开始运行。
该官方小例有一缺点,线程3运行时仍然被抢占了,但是只有这样子才能更好反映出线程3优先级被抬升的结果。
启发
优先级翻转现象提醒编程人员对共享资源进行互斥访问的代码段应尽量短!