一个警告背后的优先级反转问题

412 阅读2分钟

问题

最近跑项目的时候发现了一个紫色的警告⚠️,只有在Run阶段才有。

image.png

Thread running at QOS_CLASS_USER_INITIATED waiting on a lower QoS thread running at QOS_CLASS_DEFAULT. Investigate ways to avoid priority inversions

分析

警告翻译过来的意思是:运行在QOS_CLASS_USER_INITIATED的线程正在等待运行在QOS_CLASS_DEFAULT的低QoS线程。研究避免优先级反转的方法

显然这是个优先级反转的问题,关于QoS,Foundation框架中有个枚举

typedef NS_ENUM(NSInteger, NSQualityOfService) {

    NSQualityOfServiceUserInteractive = 0x21,

    NSQualityOfServiceUserInitiated = 0x19,

    NSQualityOfServiceUtility = 0x11,

    NSQualityOfServiceBackground = 0x09,

    NSQualityOfServiceDefault = -1

} API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0));

其中NSQualityOfServiceDefaultNSQualityOfServiceUserInitiated恰好和警告中的QOS_CLASS_DEFAULTQOS_CLASS_USER_INITIATED对应。

警告发生的地方运行的线程的qos为QOS_CLASS_USER_INITIATED,但在[[BMKMapManager alloc] init]里面有等待其他线程qos为QOS_CLASS_DEFAULT的操作。

cpu在分配权限的时候会根据qos的等级优先分配,QOS_CLASS_USER_INITIATED的级别比QOS_CLASS_DEFAULT更高,会抢QOS_CLASS_DEFAULT的资源,但它抢到资源也没有什么用,因为需要等待 QOS_CLASS_DEFAULT中的操作完成才行,在资源紧张的情况下,系统把资源都给了QOS_CLASS_DEFAULT,没有足够资源给QOS_CLASS_DEFAULT,导致互相等待。

尝试复现

尝试写一个类似的警告

- (void)hangRiskTest {
    //----主线程----
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.qualityOfService = NSQualityOfServiceUserInitiated;
    NSInvocationOperation *op = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(runInQoSInit) object:nil];
    [queue addOperation:op];
}

- (void)runInQoSInit {
    self.semaphore = dispatch_semaphore_create(0);
   // ---- QOS_CLASS_USER_INITIATED ---
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    queue.qualityOfService = NSQualityOfServiceDefault;
    NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
        // ----NSQualityOfServiceDefault
        NSLog(@"hangRiskTest_111");
        usleep(arc4random_uniform(30));
        dispatch_semaphore_signal(self.semaphore);
        NSLog(@"hangRiskTest_222");
        
    }];
    [queue addOperation:op];
    dispatch_wait(self.semaphore, DISPATCH_TIME_FOREVER);
    NSLog(@"hangRiskTest_3333");
}

等待代码运行过这段代码后,就会出现这个警告。注意,这种蓝色的警告只有在程序运行完才能显示。

image.png

runInQoSInit方法里面的代码是运行在优先级为NSQualityOfServiceUserInitiated线程中的,相对而言,这个优先级是比较高的。159-164行代码是运行在优先级为NSQualityOfServiceDefault线程中的。但两个线程都要操作信号量semaphore,低优先级的线程阻塞了高优先级代码的运行,这种情形就会出现警告。

结尾

回过头看项目上的警告,目前看是没有办法消除的,但通过对该问题的探究,对线程的优先级有了进一步的了解,还是多多少少有点收获的,特此记录,便于日后回顾,欢迎各位看官点赞评论。

参考资料