龙空技术网

加锁造成的线程优先级反转

编程人生 5

前言:

目前姐妹们对“什么叫优先级反转以及解决方法”大概比较注意,看官们都想要知道一些“什么叫优先级反转以及解决方法”的相关知识。那么小编在网上网罗了一些有关“什么叫优先级反转以及解决方法””的相关资讯,希望朋友们能喜欢,兄弟们快快来了解一下吧!

优先级反转(Priority Inversion),也称优先级翻转,一般是在优先级不同的多线程环境中发生。在桌面操作系统中,线程的优先级不是太重要,因此较少见优先级反转的现象。但是,优先级反转是实时操作系统(RTOS)中一个常见的问题,特别是在采用优先级调度算法的系统中。这个问题通常发生在多个线程共享一个资源(如一个互斥锁或信号量)时,低优先级的任务意外地阻塞了高优先级的任务。

假设有3个线程,thread1、thread2 和 thread3,它们的优先级依次为thread1 < thread2 < thread3。同时运行这3个线程,且只有 thread1 和 thread3 需要占用资源A。当 thread1 执行时,占用了资源A,并且未释放资源A,这种情况下,发生线程切换时,优先执行thread3,但是,由于 thread3 需要占用资源A,而资源A被 thread1 占用并未被释放,因此,thread3 需要等待 thread1 执行释放资源A。但是,由于 thread2 的优先级比 thread1 要高,导致线程切换时,优先执行 thread2,导致 thread1 无法执行,这样看起来就像 thread3 需要等待 thread2 执行完毕,才能执行 threa3,看起来就像thread2 的优先级 > thread3的优先级。这种现象就被称为优先级反转。

以下代码演示了优先级不同的3个线程,执行相同的任务,优先级最高的线程获得的CPU时间最多,最先执行完毕,优先级最低的线程获得的CPU时间最少,最后执行完毕:

#include <chrono>#include <iostream>#include <mutex>#include <pthread.h>#include <unistd.h>void consumeTime(){    int iMax = 2000000;    int jMax = 10000;    for(int i = 0; i < iMax; i++)        for(int j = 0; j < jMax; j++);}std::int64_t getCurTs(){    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count();}static void* thread1(void * par){    std::cout << "thread1 begin" << std::endl;    std::int64_t tms = getCurTs();    consumeTime();    std::int64_t tme = getCurTs();    int span = (int)(tme - tms);    std::cout << "thread1 end , duration : " << span << " ms " << std::endl;}static void* thread2(void * par){    std::cout << "thread2 begin" << std::endl;    std::int64_t tms = getCurTs();    consumeTime();    std::int64_t tme = getCurTs();    int span = (int)(tme - tms);    std::cout << "thread2 end , duration : " << span << " ms " << std::endl;}static void* thread3(void * par){    std::cout << "thread3 begin" << std::endl;    std::int64_t tms = getCurTs();    consumeTime();    std::int64_t tme = getCurTs();    int span = (int)(tme - tms);    std::cout << "thread3 end , duration : " << span << " ms " << std::endl;}int main(int argc, char *argv[]){    //进程绑定cpu0运行        pid_t pid = getpid();        cpu_set_t cpuset;    CPU_ZERO(&cpuset);    CPU_SET(0, &cpuset);    sched_setaffinity(pid, sizeof(cpu_set_t), &cpuset);    //创建3个线程        pthread_attr_t attr1;    pthread_attr_t attr2;    pthread_attr_t attr3;        if(pthread_attr_init(&attr1) != 0 ||       pthread_attr_init(&attr2) != 0 ||       pthread_attr_init(&attr3) != 0)        return -1;            pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);    pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);    pthread_attr_setinheritsched(&attr3, PTHREAD_EXPLICIT_SCHED);        pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);    pthread_attr_setschedpolicy(&attr2, SCHED_FIFO);    pthread_attr_setschedpolicy(&attr3, SCHED_FIFO);        struct sched_param sched_param1;    struct sched_param sched_param2;    struct sched_param sched_param3;        //设置进程优先级    sched_param1.sched_priority = 5;    sched_param2.sched_priority = 6;    sched_param3.sched_priority = 7;        pthread_attr_setschedparam(&attr1, &sched_param1);    pthread_attr_setschedparam(&attr2, &sched_param2);    pthread_attr_setschedparam(&attr3, &sched_param3);        pthread_create(&t1, &attr1, thread1, NULL);    pthread_create(&t2, &attr2, thread2, NULL);    pthread_create(&t3, &attr3, thread3, NULL);    while(true)    {        usleep(5000000);    }    return 0;}

以上代码输出如下:

由上图可见,代码执行3次,每次执行,3个线程都是按照优先级高低,thread3最先执行完毕,thread2其次,thread1最后执行完毕。

为了模拟优先级反转的效果,将以上代码做一些修改,得到以下代码,在线程1和线程3之中,加上了加锁和解锁,用于模拟共享资源A:

#include <chrono>#include <iostream>#include <mutex>#include <pthread.h>#include <unistd.h>std::mutex mutex;void consumeTime(bool share){    int iMax = 2000000;    int jMax = 10000;    for(int i = 0; i < iMax; i++)    {        if(share)            mutex.lock();        for(int j = 0; j < jMax; j++);        if(share)            mutex.unlock();    }}std::int64_t getCurTs(){    return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock().now().time_since_epoch()).count();}static void* thread1(void * par){    std::cout << "thread1 begin" << std::endl;    std::int64_t tms = getCurTs();    consumeTime(true);    std::int64_t tme = getCurTs();    int span = (int)(tme - tms);    std::cout << "thread1 end , duration : " << span << " ms " << std::endl;}static void* thread2(void * par){    std::cout << "thread2 begin" << std::endl;    std::int64_t tms = getCurTs();    consumeTime(false);    std::int64_t tme = getCurTs();    int span = (int)(tme - tms);    std::cout << "thread2 end , duration : " << span << " ms " << std::endl;}static void* thread3(void * par){    std::cout << "thread3 begin" << std::endl;    std::int64_t tms = getCurTs();    consumeTime(true);    std::int64_t tme = getCurTs();    int span = (int)(tme - tms);    std::cout << "thread3 end , duration : " << span << " ms " << std::endl;}int main(int argc, char *argv[]){    //进程绑定cpu0运行    pid_t pid = getpid();    cpu_set_t cpuset;    CPU_ZERO(&cpuset);    CPU_SET(0, &cpuset);    sched_setaffinity(pid, sizeof(cpu_set_t), &cpuset);    //创建3个线程    pthread_attr_t attr1;    pthread_attr_t attr2;    pthread_attr_t attr3;    if(pthread_attr_init(&attr1) != 0 ||       pthread_attr_init(&attr2) != 0 ||       pthread_attr_init(&attr3) != 0)        return -1;    pthread_attr_setinheritsched(&attr1, PTHREAD_EXPLICIT_SCHED);    pthread_attr_setinheritsched(&attr2, PTHREAD_EXPLICIT_SCHED);    pthread_attr_setinheritsched(&attr3, PTHREAD_EXPLICIT_SCHED);    pthread_attr_setschedpolicy(&attr1, SCHED_FIFO);    pthread_attr_setschedpolicy(&attr2, SCHED_FIFO);    pthread_attr_setschedpolicy(&attr3, SCHED_FIFO);    struct sched_param sched_param1;    struct sched_param sched_param2;    struct sched_param sched_param3;    sched_param1.sched_priority = 5;    sched_param2.sched_priority = 6;    sched_param3.sched_priority = 7;    pthread_attr_setschedparam(&attr1, &sched_param1);    pthread_attr_setschedparam(&attr2, &sched_param2);    pthread_attr_setschedparam(&attr3, &sched_param3);    pthread_create(&t1, &attr1, thread1, NULL);    pthread_create(&t2, &attr2, thread2, NULL);    pthread_create(&t3, &attr3, thread3, NULL);    while(true)    {        usleep(5000000);    }    return 0;}

以上代码输出如下:

由上图可见,代码执行3次,每次执行,都是thread2最先执行完毕,主要原因,就是thread3中加锁时会等待thread1解锁完毕,看起来就像是thread2优先级比thread3高。

为了避免优先级反转问题,一般可以采取以下几种策略:

1.优先级继承(Priority Inheritance):

当一个低优先级任务持有锁时,如果高优先级任务请求该锁但未能获得,则将锁持有的任务的优先级提升到请求锁的任务的优先级,这样就可以让持锁的任务尽快完成并释放锁。

2.优先级天花板(Priority Ceiling):

这种方法规定了一个任务在获取锁时将被提升到一个固定的“天花板”优先级,这个优先级至少不低于任何可能请求该锁的任务的优先级。当任务释放锁时,它的优先级会恢复到原来的水平。

3.优先级继承协议(Priority Inheritance Protocol,PIP):

这是一种更通用的方法,它不仅提升了持有锁的任务的优先级,还确保了优先级的传递性,即如果一个任务 T1 正在等待另一个任务 T2 释放锁,而 T2 又在等待 T3 释放锁,那么 T2 和 T3 的优先级都会被提升到与 T1 相同的优先级。

4.使用信号量:

使用二进制信号量或者计数型信号量可以有效地管理对共享资源的访问,从而避免优先级反转问题。

在实际的嵌入式系统设计中,选择哪种机制取决于系统的特定需求和资源限制。例如,在某些RTOS中,优先级继承是默认启用的,而在其他系统中则需要手动配置。

总之,优先级反转是一个需要注意的问题,因为它会影响系统的响应时间和实时性能。通过合理的设计和选择合适的解决方案,可以有效避免这个问题的发生,保证系统的稳定性和可靠性。

标签: #什么叫优先级反转以及解决方法