当前位置: 首页 > news >正文

「iOS」————优先级反转

iOS学习

    • 优先级反转问题
      • 优先级反转的后果
        • Bounded priority inversion
        • Unbounded priority inversion
        • 解决方法
          • 面试题


优先级反转问题

什么是优先级反转?

优先级反转是指:某同步资源被较低优先级的进程/线程所拥有,较高优先级的线程/进程竞争改同步资源未获得改资源,而使得较高优先级进程/线程反而推迟被调度执行的现象。根据阻塞类型的不同,优先级反转又被分为Bounded priority inversionUnbounded priority inversion

优先级反转的后果

  • 低优先级的任务比高优先级的任务先执行,导致任务的错乱,逻辑错乱;
  • 可能造成系统崩溃;
  • 死锁;优先级低的线程迟迟得不到调度,具有高优先级的线程不能执行,死锁;
Bounded priority inversion

高优先级任务(Task H)被持有锁的低优先级任务(Task L)阻塞,由于阻塞的时间取决于低优先级任务在临界区的时间(持有锁的时间),所以被称为bounded priority inversion。只要Task L一直持有锁,Task H就会一直被阻塞,低优先级的任务运行在高优先级任务的前面,优先级被反转。

这里的任务也可以理解为线程

图片

// PriorityInversionDemo.m
#import <Foundation/Foundation.h>
#import <pthread.h>
#import <unistd.h>pthread_mutex_t mutex;void *lowPriorityTask(void *arg) {NSLog(@"[L] 低优先级任务启动,准备加锁");pthread_mutex_lock(&mutex);NSLog(@"[L] 低优先级任务获得锁,进入临界区");sleep(3); // 模拟长时间占用临界区NSLog(@"[L] 低优先级任务即将释放锁");pthread_mutex_unlock(&mutex);NSLog(@"[L] 低优先级任务结束");return NULL;
}void *mediumPriorityTask(void *arg) {sleep(1); // 确保低优先级先获得锁NSLog(@"[M] 中优先级任务启动,开始忙等");for (int i = 0; i < 5; i++) {NSLog(@"[M] 中优先级任务运行中...");usleep(500 * 1000); // 0.5秒}NSLog(@"[M] 中优先级任务结束");return NULL;
}void *highPriorityTask(void *arg) {sleep(2); // 确保低优先级先获得锁NSLog(@"[H] 高优先级任务启动,准备加锁");pthread_mutex_lock(&mutex);NSLog(@"[H] 高优先级任务获得锁,进入临界区");pthread_mutex_unlock(&mutex);NSLog(@"[H] 高优先级任务结束");return NULL;
}int main(int argc, const char * argv[]) {@autoreleasepool {pthread_mutex_init(&mutex, NULL);pthread_t low, medium, high;// 创建低优先级线程pthread_create(&low, NULL, lowPriorityTask, NULL);// 创建中优先级线程pthread_create(&medium, NULL, mediumPriorityTask, NULL);// 创建高优先级线程pthread_create(&high, NULL, highPriorityTask, NULL);// 等待线程结束pthread_join(low, NULL);pthread_join(medium, NULL);pthread_join(high, NULL);pthread_mutex_destroy(&mutex);}return 0;
}

请添加图片描述

上述代码用 pthread_mutex_t 互斥锁演示了 bounded priority inversion。低优先级线程持有锁,高优先级线程被阻塞,而中优先级线程频繁运行导致低优先级线程无法及时释放锁,形成优先级反转。只要低优先级线程能运行并释放锁,反转就会结束,这就是“有界”的含义。

Unbounded priority inversion

Task L持有锁的情况下,如果有一个中间优先级的任务(Task M)打断了Task L,前面的bounded就会变为unbounded,因为Task M只要抢占了Task LCPU,就可能会阻塞Task H任意多的时间(Task M可能不止1个)。

图片

我们写个demo来验证:

// UnboundedPriorityInversionDemo.m
#import <Foundation/Foundation.h>
#import <pthread.h>
#import <unistd.h>pthread_mutex_t mutex;void *lowPriorityTask(void *arg) {NSLog(@"[L] 低优先级任务启动,准备加锁");pthread_mutex_lock(&mutex);NSLog(@"[L] 低优先级任务获得锁,进入临界区");sleep(2); // 模拟临界区操作NSLog(@"[L] 低优先级任务即将释放锁");pthread_mutex_unlock(&mutex);NSLog(@"[L] 低优先级任务结束");return NULL;
}void *mediumPriorityTask(void *arg) {sleep(1); // 确保低优先级先获得锁NSLog(@"[M] 中优先级任务启动,开始长时间运行");for (int i = 0; i < 10; i++) {NSLog(@"[M] 中优先级任务运行中...%d", i);usleep(500 * 1000); // 0.5秒}NSLog(@"[M] 中优先级任务结束");return NULL;
}void *highPriorityTask(void *arg) {sleep(1); // 确保低优先级先获得锁NSLog(@"[H] 高优先级任务启动,准备加锁");pthread_mutex_lock(&mutex);NSLog(@"[H] 高优先级任务获得锁,进入临界区");pthread_mutex_unlock(&mutex);NSLog(@"[H] 高优先级任务结束");return NULL;
}int main(int argc, const char * argv[]) {@autoreleasepool {pthread_mutex_init(&mutex, NULL);pthread_t low, medium, high;// 创建低优先级线程pthread_create(&low, NULL, lowPriorityTask, NULL);// 创建高优先级线程pthread_create(&high, NULL, highPriorityTask, NULL);// 创建中优先级线程pthread_create(&medium, NULL, mediumPriorityTask, NULL);// 等待线程结束pthread_join(low, NULL);pthread_join(medium, NULL);pthread_join(high, NULL);pthread_mutex_destroy(&mutex);}return 0;
}

按照预想中的逻辑:

  • 低优先级线程 L 先获得锁,进入临界区。

  • 高优先级线程 H 启动后,尝试加锁,被阻塞。

  • 中优先级线程 M 启动后,不断运行,占用 CPU,导致 L 得不到运行机会,H 也无法获得锁。

  • 只要 M 持续运行,H 就会被无限期阻塞,形成unbounded priority inversion。

我们实际跑一下:

请添加图片描述

发现并不是,中优先级并不能一直抢占线程。这是因为:

  • 普通用户态线程(如 macOS 下的 pthread)默认不会严格抢占,优先级只是一个“建议”,不是强制的。

  • 线程的实际调度还受到系统负载、CPU 核心数、线程创建顺序等影响。

解决方法

目前解决Unbounded priority inversion2种方法:一种被称作优先权极限(priority ceiling protocol),另一种被称作优先级继承(priority inheritance)。

优先权极限(priority ceiling protocol

系统把每一个临界资源与 1 个极限优先权相关联。当1个任务进入临界区时,系统便把这个极限优先权传递给这个任务,使得这个任务的优先权最高;当这个任务退出临界区后,系统立即把它的优先权恢复正常,从而保证系统不会出现优先权反转的情况。该极限优先权的值是由所有需要该临界资源的任务的最大优先级来决定的。

如图所示,锁的极限优先权是 3。当Task L持有锁的时候,它的优先级将会被提升到3,和Task H一样的优先级。这样就可以阻止Task M(优先级是2)的运行,直到Task LTask H不再需要该锁。

在这里插入图片描述

优先级继承(priority inheritance

大致原理是:高优先级任务在尝试获取锁的时候,如果该锁正好被低优先级任务持有,此时会临时把高优先级线程的优先级转移给拥有锁的低优先级线程,使低优先级线程能更快的执行并释放同步资源,释放同步资源后再恢复其原来的优先级。

图片

priority ceiling protocolpriority inheritance都会在释放锁的时候,恢复低优先级任务的优先级。同时要注意,以上2种方法只能阻止Unbounded priority inversion,而无法阻止Bounded priority inversionTask H必须等待Task L执行完毕才能执行,这个反转是无法避免的)。

这里还需要注意一点:优先级继承必须是可以传递的。比如说:当T1阻塞在被T2持有的资源上,而T2又阻塞在T3持有的一个资源上。如果T1的优先级高于T2T3的优先级,T3必须通过T2继承T1的优先级。否则,如果另外一个优先级高于T2T3,小于T1的线程T4,将抢占T3,引发相对于T1的优先级反转。因此,线程所继承的优先级必须是直接或者间接阻塞的线程的最高优先级。

面试题

1. 什么是优先级反转?请解释其概念并举例说明。

答:优先级反转是指一个低优先级的任务持有了一个共享资源的锁,而高优先级的任务等待该资源释放,此时中优先级的任务运行并抢占了低优先级的CPU先执行了,会导致高优先级的任务被“反转”到中优先级任务之后运行。

举例:

假设有三个任务A、B、C,它们的优先级分别为高、中、低。任务C持有一个锁并正在运行,任务A需要该锁才能运行,但任务B抢占了CPU时间。这会导致,原本任务A优先级更高,应该在任务B前执行,却导致任务A必须等待任务B完成后才能运行。

2. 在iOS开发中,如何避免或解决优先级反转问题?

  1. 优先级继承 : 确保持有锁的低优先级任务提升到高优先级任务的级别,直到释放锁,再恢复成原来的优先级。(记录了锁持有者的api都可以自动避免优先级反转,系统会通过提高相关线程的优先级来解决优先级反转的问题,如 dispatch_sync)
  2. 避免使用dispatch_semphore(信号量)做线程同步:dispatch_semaphore 容易造成优先级反转,因为api没有记录是哪个线程持有了信号量,所以有高优先级的线程在等待锁的时候,内核无法知道该提高那个线程的优先级(QoS);
  3. 使用合适的锁机制:选择合适的锁机制(如NSLock、NSRecursiveLock等)和避免长时间持有锁。
  4. 避免锁竞争:减少共享资源的使用和锁的粒度,避免长时间锁竞争。

这里解释一下第二点:

如果当前线程因等待某线程(线程 1)上正在进行的操作(如 block1)而受阻,而系统知道 block1 所在的目标线程(owner),系统会通过提高相关线程的优先级来解决优先级反转的问题。反之如果系统不知道 block1 所在目标线程,则无法知道应该提高谁的优先级,也就无法解决反转问题;

记录了持有者信息(owner)的系统 API 如下:

  1. pthread mutexos_unfair_lock、以及基于这二者实现的上层API

    a. dispatch_once 的实现是基于 os_unfair_lock

    b. NSLockNSRecursiveLock@synchronized 等的实现是基于 pthreadmutex

  2. dispatch_syncdispatch_wait

  3. xpc_connection_send_with_message_sync

使用以上API即可

http://www.lryc.cn/news/614510.html

相关文章:

  • Redis是单线程性能还高的原因
  • Redis缓存击穿、穿透雪崩
  • 【递归完全搜索】USACO Bronze 2018 December - 往返搬运Back and Forth
  • Python字典高阶操作:高效提取子集的技术与工程实践
  • RAG初步实战:从 PDF 到问答:我的第一个轻量级 RAG 系统(附详细项目代码内容与说明)
  • React 状态管理入门:从 useState 到复杂状态逻辑
  • React+TypeScript代码注释规范指南
  • HTML5 Web Workers 深度剖析:助力网页性能飞速提升
  • 3- Python 网络爬虫 — 如何抓取动态加载数据?Ajax 原理与实战全解析
  • 亚马逊广告运营如何平衡ASIN投放和关键词投放
  • 1688 图片搜图找货接口开发实战:从图像特征提取到商品匹配全流程
  • 塑料可回收物检测数据集-10,000 张图片 智能垃圾分类系统 环保回收自动化 智慧城市环卫管理 企业环保合规检测 教育环保宣传 供应链包装优化
  • 快速入门flask应用(从入门到实战)
  • 客户端攻击防御:详解现代浏览器安全措施
  • 彻底解决Hewlett-Packard - USB - 4/8/2019 12:00:00 AM - 1.0.0.237问题
  • 下一代防火墙技术
  • web端-登录页面验证码的实现(springboot+vue前后端分离)超详细
  • 《Graph machine learning for integrated multi-omics analysis》
  • 从C学C++(9)——运算符重载
  • 使用Python爬虫,selenium能否替代requests?
  • 利用哥斯拉(Godzilla)进行文件上传漏洞渗透实战分析
  • 爬虫逆向之雷池waf
  • 使用 PicGo 与 GitHub 搭建高效图床,并结合 Local Images Plus 备份原图
  • Kiro :从“规范”到“实现”的全流程 AI 助手
  • 线程池分析与设计
  • 豆包新模型+PromptPilot:AI应用开发全流程实战指南
  • 图片识别表格工具v3.0绿色版,PNG/JPG秒变可编辑Excel
  • 深入理解模板方法模式:框架设计的“骨架”艺术
  • Shell解释器
  • $QAXHoneypot是什么文件夹