优先级反转问题(含解决案例)

优先级反转(Priority Inversion)是实时系统中一个经典问题:当高优先级任务因等待低优先级任务占用的资源而被阻塞时,一个中优先级任务抢占CPU,导致高优先级任务迟迟无法运行。互斥量配合优先级继承协议可以解决这一问题:当低优先级任务持有锁且高优先级任务开始等待时,低优先级任务临时继承高优先级任务的优先级,从而防止中优先级任务抢占,使低优先级任务尽快释放锁。

以下是一个基于 POSIX 线程(pthread) 的完整实现案例,演示优先级反转的发生和通过 优先级继承互斥量 的解决过程。


实现案例(C语言,Linux)

复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <sched.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

/* 定义三个优先级(数值越小优先级越低,SCHED_FIFO 中数值越大优先级越高) */
#define PRIO_LOW    10
#define PRIO_MID    20
#define PRIO_HIGH   30

/* 共享资源模拟:仅是一个全局变量,访问前需加锁 */
int shared_resource = 0;
pthread_mutex_t mutex;

/* 用于标记是否启用优先级继承(通过不同的互斥量属性) */
int use_priority_inheritance = 0;

/* 辅助函数:设置线程的实时调度策略和优先级 */
void set_thread_priority(pthread_t thread, int policy, int priority) {
    struct sched_param param;
    param.sched_priority = priority;
    int ret = pthread_setschedparam(thread, policy, &param);
    if (ret != 0) {
        fprintf(stderr, "pthread_setschedparam error: %s\n", strerror(ret));
    }
}

/* 获取当前时间(微秒级),用于打印日志 */
long long get_usec() {
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    return ts.tv_sec * 1000000LL + ts.tv_nsec / 1000;
}

/* 低优先级线程:先获取锁,然后长时间占用 */
void* low_priority_task(void* arg) {
    long long start = get_usec();
    printf("[%lld] Low thread: trying to lock mutex\n", start);
    pthread_mutex_lock(&mutex);
    printf("[%lld] Low thread: locked mutex, start working (hold for 2s)\n", get_usec());
    /* 模拟长时间占用资源 */
    sleep(2);
    printf("[%lld] Low thread: releasing mutex\n", get_usec());
    pthread_mutex_unlock(&mutex);
    return NULL;
}

/* 中优先级线程:纯 CPU 密集型任务,不涉及锁 */
void* medium_priority_task(void* arg) {
    long long start = get_usec();
    printf("[%lld] Medium thread: started, spinning for 1.5s\n", start);
    /* 忙等待 1.5 秒,模拟高计算负载 */
    long long deadline = get_usec() + 1500000;
    while (get_usec() < deadline);
    printf("[%lld] Medium thread: finished spinning\n", get_usec());
    return NULL;
}

/* 高优先级线程:需要访问共享资源,尝试加锁 */
void* high_priority_task(void* arg) {
    /* 稍微延时,确保低优先级线程先获得锁 */
    usleep(100000);
    long long start = get_usec();
    printf("[%lld] High thread: trying to lock mutex\n", start);
    pthread_mutex_lock(&mutex);
    printf("[%lld] High thread: locked mutex, accessing resource\n", get_usec());
    shared_resource++;
    printf("[%lld] High thread: done, releasing mutex\n", get_usec());
    pthread_mutex_unlock(&mutex);
    return NULL;
}

/* 创建互斥量:根据 use_priority_inheritance 标志决定是否启用优先级继承 */
void init_mutex() {
    pthread_mutexattr_t attr;
    pthread_mutexattr_init(&attr);
    if (use_priority_inheritance) {
        /* 设置协议为 PTHREAD_PRIO_INHERIT,启用优先级继承 */
        pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT);
        printf("=== Using mutex with PRIORITY INHERITANCE ===\n");
    } else {
        /* 默认协议(PTHREAD_PRIO_NONE),会发生优先级反转 */
        printf("=== Using normal mutex (priority inversion will occur) ===\n");
    }
    pthread_mutex_init(&mutex, &attr);
    pthread_mutexattr_destroy(&attr);
}

int main(int argc, char* argv[]) {
    /* 检查是否启用优先级继承,通过命令行参数控制 */
    if (argc > 1 && strcmp(argv[1], "--inherit") == 0) {
        use_priority_inheritance = 1;
    }

    /* 初始化互斥量 */
    init_mutex();

    pthread_t low, mid, high;

    /* 设置调度策略为 SCHED_FIFO(实时调度),需要 root 权限或相应 capability */
    struct sched_param sched_param_main = { .sched_priority = PRIO_HIGH };
    if (pthread_setschedparam(pthread_self(), SCHED_FIFO, &sched_param_main) != 0) {
        perror("pthread_setschedparam for main failed. Try running with sudo.");
        exit(1);
    }

    /* 创建线程(默认调度参数,之后单独设置优先级) */
    pthread_create(&low, NULL, low_priority_task, NULL);
    pthread_create(&mid, NULL, medium_priority_task, NULL);
    pthread_create(&high, NULL, high_priority_task, NULL);

    /* 分别设置三个线程的实时优先级 */
    set_thread_priority(low, SCHED_FIFO, PRIO_LOW);
    set_thread_priority(mid, SCHED_FIFO, PRIO_MID);
    set_thread_priority(high, SCHED_FIFO, PRIO_HIGH);

    /* 等待所有线程结束 */
    pthread_join(low, NULL);
    pthread_join(mid, NULL);
    pthread_join(high, NULL);

    pthread_mutex_destroy(&mutex);
    return 0;
}

编译与运行

bash 复制代码
gcc -o priority_inversion priority_inversion.c -lpthread
# 运行普通互斥量(演示优先级反转)
sudo ./priority_inversion
# 运行启用优先级继承的互斥量(解决问题)
sudo ./priority_inversion --inherit

注意 :必须使用 sudo 或以 root 身份运行,因为 SCHED_FIFO 实时策略需要特权。


运行结果与分析

1. 普通互斥量(优先级反转发生)

典型输出如下(时间戳为微秒):

复制代码
[123456] Low thread: trying to lock mutex
[123457] Low thread: locked mutex, start working (hold for 2s)
[123557] High thread: trying to lock mutex          ← 高优先级被阻塞
[123558] Medium thread: started, spinning for 1.5s  ← 中优先级抢占 CPU
[125058] Medium thread: finished spinning
[125459] Low thread: releasing mutex                ← 低优先级释放锁
[125460] High thread: locked mutex, accessing resource
[125461] High thread: done, releasing mutex

分析 :高优先级线程在 123557 尝试加锁失败(被低优先级持有),随后中优先级线程运行长达 1.5 秒,导致高优先级等待总时间 ≈ 3.5 秒(远大于低优先级原本的 2 秒占用时间)。这就是优先级反转。

2. 启用优先级继承的互斥量

输出示例:

复制代码
[123456] Low thread: trying to lock mutex
[123457] Low thread: locked mutex, start working (hold for 2s)
[123557] High thread: trying to lock mutex          ← 高优先级等待
[123558] Low thread: (priority boosted to HIGH)     ← 实际不会打印,但内核提升低优先级
[123558] Medium thread: started, spinning for 1.5s  ← 中优先级无法抢占(因为低优先级继承了高优先级)
[125458] Low thread: releasing mutex                ← 低优先级尽快完成
[125459] High thread: locked mutex, accessing resource

分析 :当高优先级等待低优先级持有的锁时,内核将低优先级的有效优先级临时提升为高优先级(PRIO_HIGH),因此中优先级无法抢占低优先级。低优先级线程得以快速执行完临界区并释放锁,高优先级总等待时间 ≈ 低优先级原本的 2 秒(无额外延迟)。优先级反转被消除

为什么互斥量(优先级继承)能解决?

  • 优先级反转本质:低优先级持有锁时,其实际重要性被高优先级等待而"放大",但系统调度器仍按原始优先级调度,导致中优先级插队。

  • 优先级继承协议:当高优先级任务阻塞在低优先级任务持有的互斥量上时,低优先级任务临时获得高优先级的优先级(继承)。这样调度器会优先运行该低优先级任务(因为它的有效优先级已提高),使其尽快释放锁,从而减少高优先级任务的阻塞时间。

  • 互斥量实现 :在 POSIX 中通过 pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT) 启用该行为。许多实时操作系统(如 VxWorks、QNX)以及 Linux 的 futex 都支持优先级继承。


补充说明

  • 优先级天花板协议:另一种解决方案,将互斥量的优先级设置为可能访问它的最高优先级线程的优先级。与优先级继承相比,天花板协议可以避免多次继承的复杂性,但可能导致更保守的阻塞。

  • 实际应用:在实时系统中,必须为每个可能引起阻塞的共享资源使用支持优先级继承的互斥量,否则难以保证任务的最坏情况响应时间。

相关推荐
.普通人3 天前
freertos源码解析(里面的源码来源于另一个博主,我这里只是讲一下我自己的理解)
操作系统·rtos
dqsh066 天前
振兴中华之threadX RTOS移植到stm32用stm32cubeMX 保姆级教程
stm32·单片机·嵌入式硬件·rtos·threadx
Truffle7电子9 天前
STM32理论 —— FreeRTOS:中断管理、列表
stm32·单片机·嵌入式硬件·rtos
硅基导游9 天前
bpf监控某个应用里各线程锁的申请得到及释放时间
服务器·互斥锁·性能监控
Truffle7电子10 天前
STM32理论 —— FreeRTOS:任务
stm32·嵌入式·rtos
大熊背14 天前
ISP离线模式应用(二)-如何利用 ISP 离线模式 加速 3DNR 收敛
linux·算法·rtos·isp pipeline·3dnr
aspirestro三水哥15 天前
9.3工欲善其事必先利其器
rtos·xenomai
逆小舟18 天前
【SWM320】FreeRTOS搭建工程——1、框架学习
嵌入式·c·rtos
aspirestro三水哥18 天前
9.4贡献自己的第一个patch
rtos·xenomai