-
什么是优先级反转
先举一个共享资源竞争的例子:两个线程需要同时将同一个变量 V(初始值为 0)进行自增操作。解决共享资源竞争的办法是加入一把锁,在访问变量 V 前占有该锁,在访问后释放该锁。一般情况下,我们可以使用初始值为 TRUE 的二进制信号量或初始值为 1 的计数型信号量作为锁。
现在我们把这个例子稍加改动,有三个线程(线程 A、线程 B、线程 C)和一个变量 V,线程 A 和线程 B 需要同时访问变量V。很显然我们需要一把锁(即保护变量 V 的锁 L)。
- 线程 A 的优先级为 1,线程 B 的为 3,线程 C 的为 2;即优先级:线程 A>线程 C>线程B。
我们假设现在线程 A 和线程 C 处于阻塞状态,线程 B 处于运行态。线程 B 占有了锁 L(下图中的#1),这时线程 C 等待的事件到来进入就绪状态,由于线程 C 的优先级较线程B 的高,线程 C 将抢占线程 B 的执行(下图中的 #2);这时线程 A 等待的事件到来又进入就绪状态,由于线程 A 的优先级较线程 C 的高,线程 A 将抢占线程 C 并执行(下图中的#3);此时线程 A 也去申请锁 L,由于锁 L 已经被线程 B 占有,因此线程 A 必须阻塞等待锁 L 被线程 B 释放,而此时拥有中等优先级的线程 C 将继续运行(下图中的#4),此时3 个线程的运行现状为:线程 A 阻塞、线程 C 正常运行、线程 B 阻塞,这显然有违实时性原则。
一个高优先级线程通过信号量机制访问共享资源时,该信号量已被一个低优先级线程占有,而这个低优先级线程在访问共享资源时可能又被其他的一些中等优先级线程抢占,因此造成高优先级线程被许多具有较低优先级的线程阻塞,我们称此现象为优化级反转。
-
解决优先级反转的办法
解决优先级反转问题有优先级天花板(priority ceiling)和优先级继承(priority inheritance)两种办法(SylixOS 互斥信号量同时支持)。
优先级天花板是当线程申请某共享资源时,把该线程的优先级提升到可访问这个资源的所有线程中的最高优先级,这个优先级称为该资源的优先级天花板。这种方法简单易行,不必进行复杂的判断,不管线程是否阻塞了高优先级线程的运行,只要线程访问共享资源都会提升线程的优先级。
优先级继承是当线程 A 申请共享资源 V 时,如果共享资源 V 正在被线程 B 使用,通过比较线程 B 与自身的优先级,若发现线程 B 的优先级小于自身的优先级,则将线程 B 的优先级提升到自身的优先级,线程 B 释放共享资源 V 后,再恢复线程 B 的原优先级。这种方法只在占有资源的低优先级线程阻塞了高优先级线程时才动态地改变线程的优先级。
二进制信号量和计数信号量均不支持优先级天花板和优先级继承,只有互斥信号量才支持优先级天花板和优先级继承。
代码实现:关于优先级反转的模拟实现及解决方案
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
pthread_t th_flag;
pthread_attr_t pattr[5];
pthread_mutex_t mutex;
sem_t lock;
INT priority_flag;
INT priority;
INT count = 0;
void delayManu(){
int i, j;
for(i=0; i<10000; i++)
for(j=0; j<5000; j++);
}
static void *start_routine1(void *arg){
while(1){
sem_wait(&lock);
delayManu();
count++;
// fprintf(stdout, "thread:%ld priority: %d count = %d\n",(long)arg, pattr[(long)arg].schedparam.sched_priority, count);
pthread_getschedprio(pthread_self(), &priority);
fprintf(stdout, "thread:%ld priority: %d count = %d\n",(long)arg, priority, count);
sem_post(&lock);
}
return 0;
}
static void *start_routine2(void *arg){
while(1){
delayManu();
count++;
pthread_getschedprio(pthread_self(), &priority);
fprintf(stdout, "thread:%ld priority: %d count = %d\n",(long)arg, priority, count);
// API_ThreadgetPriority(API_ThreadIdSelf(), &priority);
}
return 0;
}
static void *start_routine3(void *arg){
while(1){
pthread_mutex_lock(&mutex);
delayManu();
count++;
pthread_getschedprio(pthread_self(), &priority);
fprintf(stdout, "thread:%ld priority: %d count = %d\n",(long)arg, priority, count);
// fprintf(stdout, "thread:%ld priority: %d count = %d\n",(long)arg, pattr[(long)arg].schedparam.sched_priority, count);
pthread_mutex_unlock(&mutex);
}
return 0;
}
int test1(){
pthread_t th1, th2, th3;
sem_init(&lock, 1, 1); /* 使用匿名信号量实现优先级反转 */
if ((pthread_attr_init(&pattr[1]) != 0) ||
(pthread_attr_init(&pattr[2]) != 0) ||
(pthread_attr_init(&pattr[3]) != 0))
goto _error1_;
/*
* 优先级 th1 < th2 < th3
*/
pattr[1].schedparam.sched_priority = 5;
pattr[2].schedparam.sched_priority = 6;
pattr[3].schedparam.sched_priority = 7;
/*
* 创建线程 th1、th2、th3,同时将线程标记传入线程函数内完成信息打印
*/
pthread_create(&th1, &pattr[1], start_routine1, (void *)1);
sleep(2);
pthread_create(&th2, &pattr[2], start_routine2, (void *)2);
sleep(2); /* 阻塞th3运行,保证th2抢占th1 */
pthread_create(&th3, &pattr[3], start_routine1, (void *)3);
printf("test1 end!\n");
return (0);
_error1_:
fprintf(stderr, "initialize failed\n");
return -1;
}
int test2(){
pthread_t th1, th2, th3, th4;
pthread_mutexattr_t mutexattr;
int prioceiling = 100;
if(pthread_mutexattr_init(&mutexattr))
goto _error2_;
// pthread_mutexattr_setprotocol(&mutexattr, PTHREAD_PRIO_NONE); /* 优先级继承,按先入先出顺序等待 */
// pthread_mutexattr_setprotocol(&mutexattr, PTHREAD_PRIO_INHERIT); /* 优先级继承,按优先级顺序等待 */
pthread_mutexattr_setprotocol(&mutexattr, PTHREAD_PRIO_PROTECT); /* 优先级天花板 */
pthread_mutex_init(&mutex, &mutexattr);
// pthread_mutex_setprioceiling(&mutex, prioceiling);
pthread_mutex_getprioceiling(&mutex, &prioceiling);
fprintf(stdout, "mutex prioceiling is: %d\n", prioceiling);
if(pthread_mutexattr_destroy(&mutexattr))
goto _error2_;
if ((pthread_attr_init(&pattr[1]) != 0) ||
(pthread_attr_init(&pattr[2]) != 0) ||
(pthread_attr_init(&pattr[3]) != 0) ||
(pthread_attr_init(&pattr[4]) != 0))
goto _error2_;
/*
* 优先级 th1 < th2 < th3,实现 th2 抢占 th1 ,以此阻塞 th3
*/
pattr[1].schedparam.sched_priority = 5;
pattr[2].schedparam.sched_priority = 6;
pattr[3].schedparam.sched_priority = 7;
pattr[4].schedparam.sched_priority = 7;
/*
* 创建线程 th1、th2、th3、th4,同时将线程标记传入线程函数内完成信息打印
*/
pthread_create(&th1, &pattr[1], start_routine3, (void *)1);
sleep(2);
pthread_create(&th2, &pattr[2], start_routine2, (void *)2);
sleep(2); /* 阻塞th3运行,保证th2抢占th1 */
pthread_create(&th3, &pattr[3], start_routine3, (void *)3);
sleep(2);
pthread_create(&th4, &pattr[4], start_routine3, (void *)4);
pthread_mutex_destroy(&mutex);
printf("test2 end!\n");
return 0;
_error2_:
fprintf(stderr, "initialize failed\n");
return -1;
}
int main (int argc, char **argv)
{
pid_t pid;
// iCpuMax = sysconf(_SC_NPROCESSORS_ONLN);
pid = getpid();
cpu_set_t cpuset;
CPU_ZERO(&cpuset);
CPU_SET(0, &cpuset);
sched_setaffinity(pid, sizeof(cpu_set_t), &cpuset);
// test1(); /* 优先级反转 - 场景模拟 */
test2(); /* 优先级反转 - 解决方案 */
return 0;
}