这一节的内容主要是围绕上一节互斥量优先继承和优先级反转的问题进一步进行扩展探讨;
一、线程优先级翻转
优先级翻转:是使用信号量会导致的另一个潜在问题;所谓线程优先级翻转,即当一个高优先级线程试图通过某种互斥IPC对象机制访问共享资源时,如果该IPC对象已被一低优先级的线程所持有,这个低优先级线程在运行过程中,可能又被其他一些其他中优先级的线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞的情况。优先级翻转会造成高优先级线程的实时性得不到保证。
二、优先级继承
在RT_thread 中,通过互斥量的优先级继承算法,可以有效的解决优先级翻转问题。
优先级继承是指提高某个占有某种共享资源的低优先级的继承的优先级,使之与所有等待该资源的线程中的优先级最高的那个线程的优先级相等,从而得到更快的执行然后释放共享资源,而当这个低优先级线程释放该资源时,优先级重新回到初始设定值。
继承优先级的线程避免了系统共享资源被任何中间优先级的线程抢占。
在这里解读一下,这张优先级继承的图案例: C在执行低优先级的资源后,A,B线程现处于被挂起状态;A属于高优先级,就绪执行了,也想访问同一线程资源,所以A运行该线程资源,当A运行完后,C处于与同一线程的共享资源,所以C的线程优先级被暂时继承了A的,所以C的优先级会比B线程先运行;当运行完后,线程回归正常状态;
三、代码案例
/*
* Copyright (c) 2006-2018, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2018-08-24 yangjie the first version
*/
/*
* 程序清单:互斥量使用例程
*
* 这个例子将创建 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("3the priority of thread2 is: %d\n", tid2->current_priority);
rt_kprintf("4the priority of thread3 is: %d\n", tid3->current_priority);
rt_kprintf("test failed.\n");
return;
}
else
{
rt_kprintf("5the priority of thread2 is: %d\n", tid2->current_priority);
rt_kprintf("6the 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("1the priority of thread3 is: %d\n", tid3->current_priority);
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.如果高优先级在进入互斥阶段前,让出线程资源,则可以被低优先级抢有;
运行结果:
程序运行解读:
1.先运行了线程1,线程1让出了线程资源;
2.接着运行线程2,所以先打印了线程2的函数,但是没进入互斥,并让出线程资源;
3.所以线程3没有睡眠,直接运行完整个程序,当线程2试图持有互斥量后,把三的线程优先级提高了,最后在线程1中显示;