优先级反转那些事儿

使用信号量造成线程优先级反转 在使用Source时,线程A等待事件到来,线程A转为就绪态,因为线程A优先级比线程C高,所以线程A会立即执行。会通过提高相关线程的优先级来解决优先级反转的问题 (如线程A在尝试获取共享资源而被挂起的期间内,将线程C的优先级提升到同线程A的优先级,等线程C处理结束,降回原优先级,这样能防止C被B抢占)。dispatch_semaphore 容易造成优先级反转,因为api没有记录是哪个线程持有了信号量,所以有高优先级的线程在等待锁的时候,内核无法知道该提高那个线程的优先级(QoS); 阅读详情

作者:崔晓兵

从一个线上问题说起

最近在线上遇到了一些[HMDConfigManager remoteConfigWithAppID:]卡死

初步分析

观察了下主线程堆栈,用到的锁是读写锁[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-q9tQjjAd-1669621412273)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/50e8b46fed714d2788a31446a192afaatplv-k3u1fbpfcp-zoom-1.image)]随后又去翻了下持有着锁的子线程,有各种各样的情况,且基本都处于正常的执行状态,例如有的处于打开文件状态,有的处于`read`状态,有的正在执行`NSUserDefaults`的方法…[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AGQOke1N-1669621412275)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3cfa4df0de8c44b48bc462e1ed891f10tplv-k3u1fbpfcp-zoom-1.image)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7yKhbpG2-1669621412276)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/9817c2ac51114fa9a8ce523761b88407tplv-k3u1fbpfcp-zoom-1.image)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-95aOc7Pj-1669621412277)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6a1f49b1e07041ff89d59fc35e0b78d7tplv-k3u1fbpfcp-zoom-1.image)]通过观察发现,出问题的线程都有QOS:BACKGROUND标记。整体看起来持有锁的子线程仍然在执行,只是留给主线程的时间不够了。为什么这些子线程在持有锁的情况下,需要执行这么久,直到主线程的8s卡死?一种情况就是真的如此耗时,另一种则是出现了优先级反转。

解决办法

在这个案例里面,持有读写锁且优先级低的线程迟迟得不到调度(又或者得到调度的时候又被抢占了,或者得到调度的时候时间已然不够了),而具有高优先级的线程由于拿不到读写锁,一直被阻塞,所以互相死锁。iOS8之后引入了QualityOfService的概念,类似于线程的优先级,设置不同的QualityOfService的值后系统会分配不同的CPU时间、网络资源和硬盘资源等,因此我们可以通过这个设置队列的优先级 。

方案一:去除对NSOperationQueue的优先级设置

在 Threading Programming Guide 文档中,苹果给出了提示:

Important: It is generally a good idea to leave the priorities of your threads at their default values. Increasing the priorities of some threads also increases the likelihood of starvation among lower-priority threads. If your application contains high-priority and low-priority threads that must interact with each other, the starvation of lower-priority threads may block other threads and create performance bottlenecks.

苹果的建议是不要随意修改线程的优先级,尤其是这些高低优先级线程之间存在临界资源竞争的情况。所以删除相关优先级设置代码即可解决问题。

方案二:临时修改线程优先级

在 pthread_rwlock_rdlock(3pthread) 发现了如下提示:

Realtime applications may encounter priority inversion when using read-write locks. The problem occurs when a high priority thread “locks” a read-write lock that is about to be “unlocked” by a low priority thread, but the low priority thread is preempted by a medium priority thread. This scenario leads to priority inversion; a high priority thread is blocked by lower priority threads for an unlimited period of time. During system design, realtime programmers must take into account the possibility of this kind of priority inversion. They can deal with it in a number of ways, such as by having critical sections that are guarded by read-write locks execute at a high priority, so that a thread cannot be preempted while executing in its critical section.

尽管针对的是实时系统,但是还是有一些启示和帮助。按照提示,对有问题的代码进行了修改:在线程通过pthread_rwlock_wrlock拿到_rwlock的时候,临时提升其优先级,在释放_rwlock之后,恢复其原先的优先级。

- (id)remoteConfigWithAppID:(NSString *)appID
{
    .......
    pthread_rwlock_rdlock(&_rwlock);
    HMDHeimdallrConfig *result = ....... // get existing config
    pthread_rwlock_unlock(&_rwlock);
    
    if(result == nil) {
        result = [[HMDHeimdallrConfig alloc] init]; // make a new config
        pthread_rwlock_wrlock(&_rwlock);
        
        qos_class_t oldQos = qos_class_self();
        BOOL needRecover = NO;
        
        // 临时提升线程优先级
        if (_enablePriorityInversionProtection && oldQos < QOS_CLASS_USER_INTERACTIVE) {
            int ret = pthread_set_qos_class_self_np(QOS_CLASS_USER_INTERACTIVE, 0);
            needRecover = (ret == 0);
        }
            
        ......

        pthread_rwlock_unlock(&_rwlock);
        
        // 恢复线程优先级
        if (_enablePriorityInversionProtection && needRecover) {
            pthread_set_qos_class_self_np(oldQos, 0);
        }
    }
    
    return result;
}

值得注意的是,这里只能使用pthread的api,NSThread提供的API是不可行的

Demo 验证

为了验证上述的手动调整线程优先级是否有一定的效果,这里通过demo进行本地实验:定义了2000个operation(目的是为了CPU繁忙),优先级设置NSQualityOfServiceUserInitiated,且对其中可以被100整除的operation的优先级调整为NSQualityOfServiceBackground,在每个operation执行相同的耗时任务,然后对这被选中的10个operation进行耗时统计。

for (int j = 0; j < 2000; ++j) {
    NSOperationQueue *operation = [[NSOperationQueue alloc] init];
    operation.maxConcurrentOperationCount = 1;
    operation.qualityOfService = NSQualityOfServiceUserInitiated;
    
    // 模块1
    // if (j % 100 == 0) {
    //    operation.qualityOfService = NSQualityOfServiceBackground;
    // }
    // 模块1
    
    [operation addOperationWithBlock:^{
        // 模块2
        // qos_class_t oldQos = qos_class_self();
        // pthread_set_qos_class_self_np(QOS_CLASS_USER_INITIATED, 0);
        // 模块2
        
        NSTimeInterval start = CFAbsoluteTimeGetCurrent();
        double sum = 0;
        for (int i = 0; i < 100000; ++i) {
            sum += sin(i) + cos(i) + sin(i*2) + cos(i*2);
        }
        start = CFAbsoluteTimeGetCurrent() - start;
        if (j % 100 == 0) {
            printf("%.8f\n", start * 1000);
        }
        
        // 模块2
        // pthread_set_qos_class_self_np(oldQos, 0);
        // 模块2
    }];
}

统计信息如下图所示

ABC
(注释模块1和模块2代码)(只打开模块1代码)(同时打开模块1和模块2代码)
11.819056194.7021018915.04005137

可以看到

  1. 正常情况下,每个任务的平均耗时为:11.8190561;
  2. 当operation被设置为低优先级时,其耗时大幅度提升为:94.70210189;
  3. 当operation被设置为低优先级时,又在Block中手动恢复其原有的优先级,其耗时已经大幅度降低:15.04005137( 耗时比正常情况高,大家可以思考下为什么)

通过Demo可以发现,通过手动调整其优先级,低优先级任务的整体耗时得到大幅度的降低,这样在持有锁的情况下,可以减少对主线程的阻塞时间。

上线效果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ILBP87yd-1669621412277)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b8f42b77c98405c918d31dda060be1b~tplv-k3u1fbpfcp-zoom-1.image)]

该问题的验证过程分为2个阶段:

  1. 第一个阶段如第1个红框所示,从3月6号开始在版本19.7上有较大幅度的下降,主要原因:堆栈中被等待的队列信息由QOS:BACKGROUND变为了com.apple.root.default-qos,队列的优先级从QOS_CLASS_BACKGROUND提升为QOS_CLASS_DEFAULT,相当于实施了方案一,使用了默认优先级。
  2. 第二个阶段如第2个红框所示,从4月24号在版本20.3上开始验证。目前看起来效果暂时不明显,推测一个主要原因是:demo中是把优先级从QOS_CLASS_BACKGROUND提升为QOS_CLASS_USER_INITIATED,而线上相当于把队列的优先级从默认的优先级QOS_CLASS_DEFAULT提升为QOS_CLASS_USER_INITIATED所以相对来说,线上的提升相对有限。
    1. QOS_CLASS_BACKGROUND的Mach层级优先级数是4;
    2. QOS_CLASS_DEFAULT的Mach层级优先级数是31;
    3. QOS_CLASS_USER_INITIATED的Mach层级优先级数是37;

深刻理解优先级反转

那么是否所有锁都需要像上文一样,手动提升持有锁的线程优先级?系统是否会自动调整线程的优先级?如果有这样的机制,是否可以覆盖所有的锁?要理解这些问题,需要深刻认识优先级反转。

什么是优先级反转?

优先级反转,是指某同步资源被较低优先级的进程/线程所拥有,较高优先级的进程/线程竞争该同步资源未获得该资源,而使得较高优先级进程/线程反而推迟被调度执行的现象。根据阻塞类型的不同,优先级反转又被分为Bounded priority inversion和Unbounded priority inversion。这里借助 Introduction to RTOS - Solution to Part 11 (Priority Inversion) 的图进行示意。

Bounded priority inversion

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W0KnL5jl-1669621412278)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/be9031c41569478dbe19378fe255da74~tplv-k3u1fbpfcp-zoom-1.image)]

Unbounded priority inversion

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YwTtsou-1669621412279)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/73774bb3725a40b5a45653960d9169ff~tplv-k3u1fbpfcp-zoom-1.image)]

优先级反转常规解决思路

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

Priority ceiling protocol

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mJtJ9JeB-1669621412279)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/37ab3d3cf55f4d8fad25b777c4a835ae~tplv-k3u1fbpfcp-zoom-1.image)]

Priority inheritance

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2NTXZ3Rj-1669621412279)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3bc32923a97448278e6dd3c4c5e56c07~tplv-k3u1fbpfcp-zoom-1.image)]

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

可以通过以下几种发生来避免或者转移Bounded priority inversion:

  1. 减少临界区的执行时间,减少Bounded priority inversion的反转耗时;
  2. 避免使用会阻塞高优先级任务的临界区资源;
  3. 专门使用一个队列来管理资源,避免使用锁。

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

如何避免优先级反转?

QoS 传递

iOS 系统主要使用以下两种机制来在不同线程(或 queue)间传递 QoS:

  • 机制1:dispatch_async
    • dispatch_async() automatically propagates the QoS from the calling thread, though it will translate User Interactive to User Initiated to avoid assigning that priority to non-main threads.
    • Captured at time of block submission, translate user interactive to user initiated. Used if destination queue does not have a QoS and does not lower the QoS (ex dispatch_async back to the main thread)
  • 机制2:基于 XPC 的进程间通信(IPC)

系统的 QoS 传递规则比较复杂,主要参考以下信息:

  • 当前线程的 QoS
  • 如果是使用 dispatch_block_create() 方法生成的 dispatch_block,则考虑生成 block 时所调用的参数
  • dispatch_async 或 IPC 的目标 queue 或线程的 QoS

调度程序会根据这些信息决定 block 以什么优先级运行。

  1. 如果没有其他线程同步地等待此 block,则 block 就按上面所说的优先级来运行。
  2. 如果出现了线程间同步等待的情况,则调度程序会根据情况调整线程的运行优先级。

如何触发优先级反转避免机制?

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

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

  1. pthread mutex、os_unfair_lock、以及基于这二者实现的上层 API
    1. dispatch_once 的实现是基于 os_unfair_lock 的
    2. NSLock、NSRecursiveLock、@synchronized 等的实现是基于 pthread mutex
  2. dispatch_sync、dispatch_wait
  3. xpc_connection_send_with_message_sync

使用以上这些 API 能够在发生优先级反转时使系统启用优先级反转避免机制。

基础API验证

接下来对前文提到的各种「基础系统API」进行验证

测试验证环境:模拟器 iOS15.2

pthread mutex

pthread mutex的数据结构pthread_mutex_s其中有一个m_tid字段,专门来记录持有该锁的线程Id。

// types_internal.h
struct pthread_mutex_s {
        long sig;
        _pthread_lock lock;
        union {
                uint32_t value;
                struct pthread_mutex_options_s options;
        } mtxopts;
        int16_t prioceiling;
        int16_t priority;
#if defined(__LP64__)
        uint32_t _pad;
#endif
        union {
                struct {
                        uint32_t m_tid[2]; // thread id of thread that has mutex locked
                        uint32_t m_seq[2]; // mutex sequence id
                        uint32_t m_mis[2]; // for misaligned locks m_tid/m_seq will span into here
                } psynch;
                struct _pthread_mutex_ulock_s ulock;
        };
#if defined(__LP64__)
        uint32_t _reserved[4];
#else
        uint32_t _reserved[1];
#endif
};

代码来验证一下:线程优先级是否会被提升?

// printThreadPriority用来打印线程的优先级信息
void printThreadPriority() {
  thread_t cur_thread = mach_thread_self();
  mach_port_deallocate(mach_task_self(), cur_thread);
  mach_msg_type_number_t thread_info_count = THREAD_INFO_MAX;
  thread_info_data_t thinfo;
  kern_return_t kr = thread_info(cur_thread, THREAD_EXTENDED_INFO, (thread_info_t)thinfo, &thread_info_count);
  if (kr != KERN_SUCCESS) {
    return;
  }
  thread_extended_info_t extend_info = (thread_extended_info_t)thinfo;
  printf("pth_priority: %d, pth_curpri: %d, pth_maxpriority: %d\n", extend_info->pth_priority, extend_info->pth_curpri, extend_info->pth_maxpriority);
}

先在子线程上锁并休眠,然后主线程请求该锁

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  printf("begin : \n");
  printThreadPriority();
  printf("queue before lock \n");
  pthread_mutex_lock(&_lock); //确保 backgroundQueue 先得到锁
  printf("queue lock \n");
  printThreadPriority();
  dispatch_async(dispatch_get_main_queue(), ^{
    printf("before main lock\n");
    pthread_mutex_lock(&_lock);
    printf("in main lock\n");
    pthread_mutex_unlock(&_lock);
    printf("after main unlock\n");
  });
  sleep(10);
  printThreadPriority();
  printf("queue unlock\n");
  pthread_mutex_unlock(&_lock);
  printf("queue after unlock\n");
});
begin : 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
queue before lock 
queue lock 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
before main lock
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63
queue unlock
in main lock
after main unlock
queue after unlock

可以看到,低优先级子线程先持有锁,当时的优先级为4,而该锁被主线程请求的时候,子线程的优先级被提升为47

os_unfair_lock

os_unfair_lock用来替换OSSpinLock,解决优先级反转问题。等待os_unfair_lock锁的线程会处于休眠状态,从用户态切换到内核态,而并非忙等。os_unfair_lock将线程ID保存到了锁的内部,锁的等待者会把自己的优先级让出来,从而避免优先级反转。验证一下:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
    printf("begin : \n");
    printThreadPriority();
    printf("queue before lock \n");
    os_unfair_lock_lock(&_unfair_lock); //确保 backgroundQueue 先得到锁
    printf("queue lock \n");
    printThreadPriority();
    dispatch_async(dispatch_get_main_queue(), ^{
      printf("before main lock\n");
      os_unfair_lock_lock(&_unfair_lock);
      printf("in main lock\n");
      os_unfair_lock_unlock(&_unfair_lock);
      printf("after main unlock\n");
    });
    sleep(10);
    printThreadPriority();
    printf("queue unlock\n");
    os_unfair_lock_unlock(&_unfair_lock);
    printf("queue after unlock\n");
  });
begin : 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
queue before lock 
queue lock 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
before main lock
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63
queue unlock
in main lock
after main unlock
queue after unlock

结果和pthread mutex一致

pthread_rwlock_t

在 pthread_rwlock_init 有如下提示:

Caveats: Beware of priority inversion when using read-write locks. A high-priority thread may be blocked waiting on a read-write lock locked by a low-priority thread. The microkernel has no knowledge of read-write locks, and therefore can’t boost the low-priority thread to prevent the priority inversion.

大意是内核不感知读写锁,无法提升低优先级线程的优先级,从而无法避免优先级反转。通过查询定义发现:pthread_rwlock_s包含了字段rw_tid,专门来记录持有写锁的线程,这不由令人好奇:为什么pthread_rwlock_s有owner信息却仍然无法避免优先级反转?

struct pthread_rwlock_s {
        long sig;
        _pthread_lock lock;
        uint32_t
                unused:29,
                misalign:1,
                pshared:2;
        uint32_t rw_flags;
#if defined(__LP64__)
        uint32_t _pad;
#endif
        uint32_t rw_tid[2]; // thread id of thread that has exclusive (write) lock
        uint32_t rw_seq[4]; // rw sequence id (at 128-bit aligned boundary)
        uint32_t rw_mis[4]; // for misaligned locks rw_seq will span into here
#if defined(__LP64__)
        uint32_t _reserved[34];
#else
        uint32_t _reserved[18];
#endif
};

https://news.ycombinator.com/item?id=21751269 链接中提到:

xnu supports priority inheritance through “turnstiles”, a kernel-internal mechanism which is used by default by a number of locking primitives (list at [1]), including normal pthread mutexes (though not read-write locks [2]), as well as the os_unfair_lock API (via the ulock syscalls). With pthread mutexes, you can actually explicitly request priority inheritance by calling pthread_mutexattr_setprotocol [3] with PTHREAD_PRIO_INHERIT; the Apple implementation supports it, but currently ignores the protocol setting and just gives all mutexes priority inheritance.

大意是:XNU使用turnstiles内核机制进行优先级继承,这种机制被应用在pthread mutex和os_unfair_lock上。

顺藤摸瓜,在ksyn_wait方法中找到了_kwq_use_turnstile的调用,其中的注释对读写锁解释的比较委婉,添加了at least sometimes

pthread mutexes and rwlocks both (at least sometimes) know their owner and can use turnstiles. Otherwise, we pass NULL as the tstore to the shims so they wait on the global waitq.

// libpthread/kern/kern_synch.c
int
ksyn_wait(ksyn_wait_queue_t kwq, kwq_queue_type_t kqi, uint32_t lockseq,
                int fit, uint64_t abstime, uint16_t kwe_flags,
                thread_continue_t continuation, block_hint_t block_hint)
{
        thread_t th = current_thread();
        uthread_t uth = pthread_kern->get_bsdthread_info(th);
        struct turnstile **tstore = NULL;
        int res;

        assert(continuation != THREAD_CONTINUE_NULL);

        ksyn_waitq_element_t kwe = pthread_kern->uthread_get_uukwe(uth);
        bzero(kwe, sizeof(*kwe));
        kwe->kwe_count = 1;
        kwe->kwe_lockseq = lockseq & PTHRW_COUNT_MASK;
        kwe->kwe_state = KWE_THREAD_INWAIT;
        kwe->kwe_uth = uth;
        kwe->kwe_thread = th;
        kwe->kwe_flags = kwe_flags;

        res = ksyn_queue_insert(kwq, kqi, kwe, lockseq, fit);
        if (res != 0) {
                //panic("psynch_rw_wrlock: failed to enqueue\n"); // XXX                ksyn_wqunlock(kwq);
                return res;
        }

        PTHREAD_TRACE(psynch_mutex_kwqwait, kwq->kw_addr, kwq->kw_inqueue,
                        kwq->kw_prepost.count, kwq->kw_intr.count);

        if (_kwq_use_turnstile(kwq)) {
                // pthread mutexes and rwlocks both (at least sometimes) know their                
                // owner and can use turnstiles. Otherwise, we pass NULL as the                
                // tstore to the shims so they wait on the global waitq.                
                tstore = &kwq->kw_turnstile;
        }
        ......
}

再去查看_kwq_use_turnstile的定义,代码还是很诚实的,只有在KSYN_WQTYPE_MTX才会启用turnstile进行优先级反转保护,而读写锁的类型为KSYN_WQTYPE_RWLOCK,这说明读写锁不会使用_kwq_use_turnstile,所以无法避免优先级反转。

#define KSYN_WQTYPE_MTX         0x01
#define KSYN_WQTYPE_CVAR        0x02
#define KSYN_WQTYPE_RWLOCK      0x04
#define KSYN_WQTYPE_SEMA        0x08

static inline bool
_kwq_use_turnstile(ksyn_wait_queue_t kwq)
{
        // If we had writer-owner information from the
        // rwlock then we could use the turnstile to push on it. For now, only
        // plain mutexes use it.
        return (_kwq_type(kwq) == KSYN_WQTYPE_MTX);
}

另外在_pthread_find_owner也可以看到,读写锁的owner是0

void
_pthread_find_owner(thread_t thread,
                struct stackshot_thread_waitinfo * waitinfo)
{
        ksyn_wait_queue_t kwq = _pthread_get_thread_kwq(thread);
        switch (waitinfo->wait_type) {
                case kThreadWaitPThreadMutex:
                        assert((kwq->kw_type & KSYN_WQTYPE_MASK) == KSYN_WQTYPE_MTX);
                        waitinfo->owner  = thread_tid(kwq->kw_owner);
                        waitinfo->context = kwq->kw_addr;
                        break;
                /* Owner of rwlock not stored in kernel space due to races. Punt
                 * and hope that the userspace address is helpful enough. */
                case kThreadWaitPThreadRWLockRead:
                case kThreadWaitPThreadRWLockWrite:
                        assert((kwq->kw_type & KSYN_WQTYPE_MASK) == KSYN_WQTYPE_RWLOCK);
                        waitinfo->owner  = 0;
                        waitinfo->context = kwq->kw_addr;
                        break;
                /* Condvars don't have owners, so just give the userspace address. */
                case kThreadWaitPThreadCondVar:
                        assert((kwq->kw_type & KSYN_WQTYPE_MASK) == KSYN_WQTYPE_CVAR);
                        waitinfo->owner  = 0;
                        waitinfo->context = kwq->kw_addr;
                        break;
                case kThreadWaitNone:
                default:
                        waitinfo->owner = 0;
                        waitinfo->context = 0;
                        break;
        }
}

把锁更换为读写锁,验证一下前面的理论是否正确:

pthread_rwlock_init(&_rwlock, NULL);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  printf("begin : \n");
  printThreadPriority();
  printf("queue before lock \n");
  pthread_rwlock_rdlock(&_rwlock); //确保 backgroundQueue 先得到锁
  printf("queue lock \n");
  printThreadPriority();
  dispatch_async(dispatch_get_main_queue(), ^{
    printf("before main lock\n");
    pthread_rwlock_wrlock(&_rwlock);
    printf("in main lock\n");
    pthread_rwlock_unlock(&_rwlock);
    printf("after main unlock\n");
  });
  sleep(10);
  printThreadPriority();
  printf("queue unlock\n");
  pthread_rwlock_unlock(&_rwlock);
  printf("queue after unlock\n");
});
begin : 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
queue before lock 
queue lock 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
before main lock
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63
queue unlock
queue after unlock
in main lock
after main unlock

可以看到读写锁不会发生优先级提升

dispatch_sync

这个API都比较熟悉了,这里直接验证:

// 当前线程为主线程
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
_queue = dispatch_queue_create("com.demo.test", qosAttribute);
printThreadPriority();
dispatch_async(_queue, ^{
    printf("dispatch_async before dispatch_sync : \n");
    printThreadPriority();
});
dispatch_sync(_queue, ^{
    printf("dispatch_sync: \n");
    printThreadPriority();
});
dispatch_async(_queue, ^{
    printf("dispatch_async after dispatch_sync: \n");
    printThreadPriority();
});
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63 
dispatch_async before dispatch_sync : 
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63
dispatch_sync: 
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63
dispatch_async after dispatch_sync: 
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63

_queue是一个低优先级队列(QOS_CLASS_BACKGROUND),可以看到dispatch_sync调用压入队列的任务,以及在这之前dispatch_async压入的任务,都被提升到较高的优先级47(和主线程一致),而最后一个dispatch_async的任务则以优先级4来执行。

dispatch_wait
// 当前线程为主线程
dispatch_queue_attr_t qosAttribute = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_BACKGROUND, 0);
_queue = dispatch_queue_create("com.demo.test", qosAttribute);
printf("main thread\n");
printThreadPriority();
dispatch_block_t block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS, ^{
    printf("sub thread\n");
    sleep(2);
    printThreadPriority();
});
dispatch_async(_queue, block);
dispatch_wait(block, DISPATCH_TIME_FOREVER);

_queue是一个低优先级队列(QOS_CLASS_BACKGROUND),当在当前主线程使用dispatch_wait进行等待时,输出如下,低优先级的任务被提升到优先级47

main thread
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63
sub thread
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63

而如果将dispatch_wait(block, DISPATCH_TIME_FOREVER)注释掉之后,输出如下:

main thread
pth_priority: 47, pth_curpri: 47, pth_maxpriority: 63
sub thread
pth_priority: 4, pth_curpri: 4, pth_maxpriority: 63

值得注意的是,dispatch_wait是一个宏(C11的泛型),或者是一个入口函数,它可以接受dispatch_block_t,dispatch_group_t,dispatch_semaphore_t 3种类型的参数,但是这里的具体含义应该是指dispatch_block_wait,只有dispatch_block_wait会调整优先级,避免优先级反转。

intptr_t
dispatch_wait(void *object, dispatch_time_t timeout);
#if __has_extension(c_generic_selections)
#define dispatch_wait(object, timeout) \
                _Generic((object), \
                        dispatch_block_t:dispatch_block_wait, \
                        dispatch_group_t:dispatch_group_wait, \
                        dispatch_semaphore_t:dispatch_semaphore_wait \
                )((object),(timeout))
#endif

神秘的信号量

dispatch_semaphore

之前对dispatch_semaphore的认知非常浅薄,经常把二值信号量和互斥锁划等号。但是通过调研后发现:dispatch_semaphore 没有 QoS 的概念,没有记录当前持有信号量的线程(owner),所以有高优先级的线程在等待锁时,内核无法知道该提高哪个线程的调试优先级(QoS)。如果锁持有者优先级比其他线程低,高优先级的等待线程将一直等待。Mutex vs Semaphore: What’s the Difference? 一文详细比对了Mutex和Semaphore之间的区别。

Semaphores are for signaling (sames a condition variables, events) while mutexes are for mutual exclusion. Technically, you can also use semaphores for mutual exclusion (a mutex can be thought as a binary semaphore) but you really shouldn’t.Right, but libdispatch doesn’t have a mutex. It has semaphores and queues. So if you’re trying to use libdispatch and you don’t want the closure-based aspect of queues, you might be tempted to use a semaphore instead. Don’t do that, use os_unfair_lock or pthread_mutex (or a higher-level construct like NSLock) instead.

这些是一些警示,可以看到dispatch_semaphore十分危险,使用需要特别小心。

这里通过苹果官方提供的demo进行解释:

__block NSString *taskName = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0); 
[self.connection.remoteObjectProxy requestCurrentTaskName:^(NSString *task) { 
     taskName = task; 
     dispatch_semaphore_signal(sema); 
}]; 
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 
return taskName;
  1. 假设在主线程执行这段代码,那么当前线程的优先级是QOS_CLASS_USER_INTERACTIVE;
  2. 由于从主线程进行了异步,异步任务队列的QoS将会被提升为QOS_CLASS_USER_INITIATED;
  3. 主线程被信号量sema阻塞,而负责释放该信号量的异步任务的优先级QOS_CLASS_USER_INITIATED低于主线程的优先级QOS_CLASS_USER_INTERACTIVE,因此可能会发生优先级反转。

值得一提的是,Clang专门针对这种情况进行了静态检测:

https://github.com/llvm-mirror/clang/blob/master/lib/StaticAnalyzer/Checkers/GCDAntipatternChecker.cpp

static auto findGCDAntiPatternWithSemaphore() -> decltype(compoundStmt()) {

  const char *SemaphoreBinding = "semaphore_name";
  auto SemaphoreCreateM = callExpr(allOf(
      callsName("dispatch_semaphore_create"),
      hasArgument(0, ignoringParenCasts(integerLiteral(equals(0))))));

  auto SemaphoreBindingM = anyOf(
      forEachDescendant(
          varDecl(hasDescendant(SemaphoreCreateM)).bind(SemaphoreBinding)),
      forEachDescendant(binaryOperator(bindAssignmentToDecl(SemaphoreBinding),
                     hasRHS(SemaphoreCreateM))));

  auto HasBlockArgumentM = hasAnyArgument(hasType(
            hasCanonicalType(blockPointerType())
            ));

  auto ArgCallsSignalM = hasAnyArgument(stmt(hasDescendant(callExpr(
          allOf(
              callsName("dispatch_semaphore_signal"),
              equalsBoundArgDecl(0, SemaphoreBinding)
              )))));

  auto HasBlockAndCallsSignalM = allOf(HasBlockArgumentM, ArgCallsSignalM);

  auto HasBlockCallingSignalM =
    forEachDescendant(
      stmt(anyOf(
        callExpr(HasBlockAndCallsSignalM),
        objcMessageExpr(HasBlockAndCallsSignalM)
           )));

  auto SemaphoreWaitM = forEachDescendant(
    callExpr(
      allOf(
        callsName("dispatch_semaphore_wait"),
        equalsBoundArgDecl(0, SemaphoreBinding)
      )
    ).bind(WarnAtNode));

  return compoundStmt(
      SemaphoreBindingM, HasBlockCallingSignalM, SemaphoreWaitM);
}

如果想使用该功能,只需要打开xcode设置即可:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GliTpcaZ-1669621412280)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/7c123a81877b4dbc919271ce9af2d90a~tplv-k3u1fbpfcp-zoom-1.image)]

另外,dispatch_group 跟 semaphore 类似,在调用 enter() 方法时,无法预知谁会调用 leave(),所以系统也无法知道其 owner是谁,所以同样不会有优先级提升的问题。

信号量卡死现身说法

dispatch_semaphore给笔者的印象非常深刻,之前写过一段这样的代码:使用信号量在主线程同步等待相机授权结果。

__block BOOL auth = NO;
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
[KTAuthorizeService requestAuthorizationWithType:KTPermissionsTypeCamera completionHandler:^(BOOL allow) {
  auth = allow;
  dispatch_semaphore_signal(semaphore);
}];
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

上线后长期占据卡死top1,当时百思不得其解,在深入了解到信号量无法避免优先级反转后,终于豁然开朗,一扫之前心中的阴霾。这类问题一般通过2种方式来解决:

  1. 使用同步API
BOOL auth = [KTAuthorizeService authorizationWithType:KTPermissionsTypeCamera];
// do something next
  1. 异步回调,不要在当前线程等待
[KTAuthorizeService requestAuthorizationWithType:KTPermissionsTypeCamera completionHandler:^(BOOL allow) {
    BOOL auth = allow;
    // do something next via callback
}];

几个概念

turnstile

前文提到XNU使用turnstile进行优先级继承,这里对turnstile机制进行简单的描述和理解。在XNU内核中,存在着大量的同步对象(例如lck_mtx_t),为了解决优先级反转的问题,每个同步对象都必须对应一个分离的数据结构来维护大量的信息,例如阻塞在这个同步对象上的线程队列。可以想象一下,如果每个同步对象都要分配一个这样的数据结构,将造成极大的内存浪费。为了解决这个问题,XNU采用了turnstile机制,一种空间利用率很高的解决方案。该方案的提出依据是同一个线程在同一时刻不能同时阻塞于多个同步对象上。这一事实允许所有同步对象只需要保留一个指向turnstile的指针,且在需要的时候去分配一个turnstile即可,而turnstile则包含了操作一个同步对象需要的所有信息,例如阻塞线程的队列、拥有这个同步对象的线程指针。turnstile是从池中动态分配的,这个池的大小会随着系统中已分配的线程数目增加而增加,所以turnstile总数将始终低于或等于线程数,这也决定了turnstile的数目是可控的。turnstile由阻塞在该同步对象上的第一个线程负责分配,当没有更多线程阻塞在该同步对象上,turnstile会被释放,回收到池中。turnstile的数据结构如下:

struct turnstile {
    struct waitq                  ts_waitq;              /* waitq embedded in turnstile */
    turnstile_inheritor_t         ts_inheritor;          /* thread/turnstile inheriting the priority (IL, WL) */
    union {
        struct turnstile_list ts_free_turnstiles;    /* turnstile free list (IL) */
        SLIST_ENTRY(turnstile) ts_free_elm;          /* turnstile free list element (IL) */
    };
    struct priority_queue_sched_max ts_inheritor_queue;    /* Queue of turnstile with us as an inheritor (WL) */
    union {
        struct priority_queue_entry_sched ts_inheritor_links;    /* Inheritor queue links */
        struct mpsc_queue_chain   ts_deallocate_link;    /* thread deallocate link */
    };
    SLIST_ENTRY(turnstile)        ts_htable_link;        /* linkage for turnstile in global hash table */
    uintptr_t                     ts_proprietor;         /* hash key lookup turnstile (IL) */
    os_refcnt_t                   ts_refcount;           /* reference count for turnstiles */
    _Atomic uint32_t              ts_type_gencount;      /* gen count used for priority chaining (IL), type of turnstile (IL) */
    uint32_t                      ts_port_ref;           /* number of explicit refs from ports on send turnstile */
    turnstile_update_flags_t      ts_inheritor_flags;    /* flags for turnstile inheritor (IL, WL) */
    uint8_t                       ts_priority;           /* priority of turnstile (WL) */

#if DEVELOPMENT || DEBUG
    uint8_t                       ts_state;              /* current state of turnstile (IL) */
    queue_chain_t                 ts_global_elm;         /* global turnstile chain */
    thread_t                      ts_thread;             /* thread the turnstile is attached to */
    thread_t                      ts_prev_thread;        /* thread the turnstile was attached before donation */
#endif
};

优先级数值

在验证环节有一些优先级数值,这里借助「Mac OS® X and iOS Internals 」解释一下:实验中涉及到的优先级数值都是相对于Mach层而言的,且都是用户线程数值

  1. 用户线程的优先级是0~63;
    1. NSQualityOfServiceBackground的Mach层级优先级数是4;
    2. NSQualityOfServiceUtility的Mach层级优先级数是20;
    3. NSQualityOfServiceDefault的Mach层级优先级数是31;
    4. NSQualityOfServiceUserInitiated的Mach层级优先级数是37;
    5. NSQualityOfServiceUserInteractive的Mach层级优先级是47;
  2. 内核线程的优先级是80~95;
  3. 实时系统线程的优先级是96~127;
  4. 64~79被保留给系统使用;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NJVxPnmq-1669621412280)(https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/756bb3e8697c47aca0df119c12f10b6e~tplv-k3u1fbpfcp-zoom-1.image)]

总结

本文主要阐述了优先级反转的一些概念和解决思路,并结合iOS平台的几种锁进行了详细的调研。通过深入的理解,可以去规避一些不必要的优先级反转,从而进一步避免卡死异常。字节跳动 APM团队也针对线程的优先级做了监控处理,进而达到发现和预防优先级反转的目的。

加入我们

字节跳动 APM 中台致力于提升整个集团内全系产品的性能和稳定性表现,技术栈覆盖iOS/Android/Server/Web/Hybrid/PC/游戏/小程序等,工作内容包括但不限于性能稳定性监控,问题排查,深度优化,防劣化等。长期期望为业界输出更多更有建设性的问题发现和深度优化手段。

欢迎对字节APM团队职位感兴趣的同学投递简历到邮箱 xushuangqing@bytedance.com 。

参考文档

  • WWDC18 What’ s New in LLVM - actorsfit
  • https://developer.apple.com/videos/play/wwdc2015/718
  • https://developer.apple.com/forums/thread/124155
  • https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/CreatingThreads/CreatingThreads.html
  • https://developer.apple.com/library/archive/documentation/Performance/Conceptual/EnergyGuide-iOS/PrioritizeWorkWithQoS.html
  • https://github.com/llvm-mirror/clang/blob/google/stable/lib/StaticAnalyzer/Checkers/ GCDAntipatternChecker.cpp
  • Don’t use dispatch semaphores where mutexes (or dispatch queues) would suffice
  • Concurrency Problems Written by Scott Grosch
  • https://www.jianshu.com/p/af64e05de503
  • https://pubs.opengroup.org/onlinepubs/7908799/xsh/pthread_rwlock_wrlock.html
  • iOS中各种“锁”的理解及应用
  • 不再安全的 OSSpinLock
  • https://blog.actorsfit.com/a?ID=00001-499b1c8e-8a7f-4960-a1c1-c8e2f42c08c6
  • https://objccn.io/issue-2-1/#Priority-Inversion
  • Introduction to RTOS - Solution to Part 11 (Priority Inversion)
  • https://threadreaderapp.com/thread/1229999590482444288.html#
  • 深入理解iOS中的锁
  • Threads can infect each other with their low priority


添加小助手回复【APM】可加入性能监控交流群,获取更多技术干货

RT-Thread ulog避坑指南:中断、HardFault和异步模式下的日志那些事儿 本文深入探讨RT-Thread ulog在中断、HardFault和异步模式下的日志处理技巧,揭示常见陷阱并提供解决方案。通过实际案例和配置建议,帮助开发者优化日志系统,确保在关键场景下可靠记录系统状态,提升调试效率。 阅读详情

相关推荐

RTOS调度算法深度解析:从抢占式到时间片轮转的嵌入式系统核心

在嵌入式系统开发中,任务调度是实时操作系统(RTOS)的核心机制,它决定了CPU时间如何分配给多个并发任务。其基本原理是通过调度算法,在多个就绪任务中动态选择下一个执行的任务,以确保系统的实时性、响应速度和资源利用率。从技术价值看,高效的调度算法能直接提升系统性能,避免任务饥饿和优先级反转等问题,是构建稳定嵌入式应用的基石。常见的应用场景包括工业控制、物联网设备、汽车电子等对实时性要求高的领域。本文聚焦于RTOS中的调度实现,深入探讨抢占式调度与时间片轮转两大基石,并结合实际开发中遇到的lvgl集成、sys

weixin_29188043的博客 295

iOS并发编程

线程 使用Instruments的CPU strategy view查看代码如何在多核CPU中执行。创建线程可以使用POSIX 线程API,或者NSThread(封装POSIX 线程API)。下面是并发4个线程在一百万个数字中找最小值和最大值的pthread例子: #import pthread.h> struct threadInfo { uint32_t * inputVa

depthlove1的专栏 2811

从FPGA到ASIC:聊聊芯片内部那些‘排队’的事儿——仲裁器设计初探

本文探讨了芯片设计中仲裁器的关键作用与实现方法,重点分析了固定优先级仲裁器(Fixed Priority Arbiter)的工作原理及其在Verilog中的实现。通过类比交通调度,解释了仲裁器如何协调多个模块对共享资源的访问,提升系统效率并避免冲突。文章还介绍了轮询调度等进阶技术,以及现代智能仲裁器的发展趋势。

weixin_42632434的博客 325

漫谈GCD

多线程是程序开发中非常基础的一个概念,大家在开发过程中应该或多或少用过相关的东西。同时这恰恰又是一个比较棘手的概念,一切跟多线程挂钩的东西都会变得复杂。如果使用过程中对多线程不够熟悉,很可能会埋下一些难以预料的坑。 iOS中的多线程技术主要有NSThread, GCD和NSOperation。他们的封装层次依次递增,其中: NSThread封...

weixin_33759269的博客 364

避坑指南:Xilinx PCIe IP的Lane顺序与GT时钟源约束那些事儿

本文深入解析Xilinx PCIe IP开发中的GT时钟约束与Lane反序问题,提供实战解决方案。针对GT参考时钟的动态管理机制和PCIe Lane物理位置反序现象,揭示官方文档未明确的约束行为,并给出验证方法和自动化流程,帮助工程师避免常见设计陷阱,提升高速接口开发效率。

weixin_30485379的博客 483

FreeModbus从机移植避坑指南:STM32/GD32串口中断与T35定时器那些事儿

本文深入解析FreeModbus从机在STM32/GD32平台移植过程中的关键问题,重点探讨串口中断与T35定时器的精准控制。通过对比STM32 HAL库与GD32标准库的差异,提供中断标志位管理、优先级设置及定时器配置的实战方案,帮助开发者避开常见陷阱,实现稳定可靠的Modbus通信。

weixin_30700099的博客 226

iOS开发进阶(六):Xcode14 使用信号量造成线程优先级反转问题修复

应用Xcode 14.1进行项目编译时,遇到以下错误提示,导致APP线程暂停。以上问题是由于iOS信号量造成线程优先级反转,在并发队列使用信号量会可能会造成线程优先级反转。经过查询资料,发现是在XCode14XCode14上默认开启的),这个工具会让APP在运行的时候,发现有例如线程优先级反转和非UI工作在主线程上运行等问题的时候,就会在XCode问题导航栏中提示该卡顿风险警告,可以帮助我们在开发初期就能发现并解决隐含的卡顿风险问题;这个不是崩溃,如果不想要,可以在 “的中去掉勾选”。XCode14。

IT全栈 华强工作室 4492

006、任务优先级与抢占式调度深入解析

RTOS任务优先级与抢占式调度实践指南 本文深入探讨RTOS任务优先级设置和抢占式调度的关键问题。通过实际案例揭示了优先级数字设置的常见误区,提出显式定义优先级偏移量的解决方案。详细解析了抢占发生的具体条件,指出只有在调度器安全点才能触发任务切换。针对优先级反转问题,给出互斥锁优先级继承机制的应对策略。文章还分享了优先级0的特殊性、实用调试技巧,以及从实战中总结的五条优先级设计原则:保留足够优先级空间、关键任务独占高优先级、慎用相同优先级、合理使用优先级继承机制,以及完善设计文档。这些经验对RTOS开发中的

weixin_56415590的博客 28

XCode14线程警告问题

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

weixin_38201792的博客 1万+

033、FreeRTOS内核对象与运行时错误排查

摘要: FreeRTOS内核对象管理不当易引发运行时错误,如野指针访问、栈溢出等。调试技巧包括启用栈检测、利用钩子函数和统计工具。设计时应明确对象所有权,删除前隔离资源,考虑静态分配。开发阶段建议全开检测功能,集中管理删除操作,并实现详尽的断言和日志记录。通过防御性编程和系统化调试方法,可有效避免内核对象相关的崩溃问题。(149字)

weixin_56415590的博客 21

基于Unity引擎利用OpenCV和MediaPipe的面部表情和人体运动捕捉系统

增加了模型的绑定,利用Mediapipe这个强大的库可以实现识别面部表情的捕捉和人体结构运动的捕捉,基于以上的特性加入到Unity的人物模型中,实现实时的面部表情和人体运行的捕捉

BIgbossyii的博客 1万+

iOS信号量造成线程优先级反转 | 京东云技术团队

在并发队列使用信号量会可能会造成线程优先级反转。

京东科技开发者 264

FileZilla Server目录显示失败的处理方法

这两天河南大暴雨挺严重导致多处停电,我远在郑州机房的服务器也断网连不上了。一直到今天下午才恢复,影响较大,有些客户要数据,可是数据因为断网拿不到。于是我想,要是能把服务器的数据备份到其他地方,比如我的本机或者另外一台电脑该多好。 于是今天一整天我都在找远程备份数据的方法,把一台服务器上的数据实时同步到另外一台上。或者两台服务器互相同步。 找了半天发现了FreeFileSync这个宝藏软件,它的原理很简单是通过ftp传输数据。 我得先在服务器上安装个ftp服务,首选filezilla server,安..

两天打鱼三天晒网 4452

FreeRTOS 实时性的那些事儿

答:低优先级任务持有互斥量;高优先级任务阻塞等待锁;中间优先级任务持续抢占 CPU。最终导致高优先级任务长时间得不到调度,响应时延不可控,直接摧毁实时性。案例:高优先级任务负责电机故障紧急保护;中优先级任务持续刷新显示屏;低优先级任务读写传感器并占用互斥锁。故障来临后,中等优先级任务不断抢占 CPU,低优先级任务无法运行释放锁,电机保护任务迟迟无法执行,极易引发设备事故。

chujing124486的博客 413

通达信主力检测利器

主力轨迹:100*EMA(EMA(MTM,N1),N1)/EMA(EMA(ABS(MTM),N1),N1),COLORWHITE;主力拉高:IF(VAR51<REF(VAR51,1),VAR51,0),COLORYELLOW,NODRAW;洗盘:IF(VAR5<REF(VAR5,1),VAR5,0),COLOR000079,NODRAW;主力进场:IF(VAR5>REF(VAR5,1),VAR5,0),COLORRED,NODRAW;MAZL:MA(主力轨迹,N2),COLORYELLOW;

zhibiaogong的博客 7451

招标文件智能解析系统开发文档

本系统是一个基于OCR与大语言模型的招标文件智能解析平台,专门用于处理和分析招标文件,自动提取关键信息并进行结构化展示。

警世龙的技术博客 1925

CH579 NVIC中断优先级配置

本文深入解析CH579基于RISC-V架构的中断机制,重点讲解PLIC如何实现高效中断管理,并通过NVIC_SetPriority等API进行优先级配置。结合UART、BLE等实际场景,指导开发者避免中断丢失、响应延迟等问题,提升系统实时性与稳定性。

weixin_30021053的博客 1032

基于YOLOv8的违规抽烟/打电话检测系统:从数据集构建到部署实战

模型优点缺点精度高速度慢(5 FPS)SSD速度快小目标检测差YOLOv5平衡性好官方支持停止YOLOv8最新、最快、最准需要PyTorch环境YOLOv8相比前代:骨干网络升级(C2f模块)无锚框检测(Anchor-Free)动态任务对齐分配(TaskAlignedAssigner)支持分类/检测/分割/姿态估计。

YOLO 97

RT-Thread 线程优先级规划实战:从理论到调试优化

本文深入探讨RT-Thread线程优先级规划实战,从理论到调试优化提供完整指南。通过finsh命令分析线程状态,识别阻塞问题,并介绍优先级动态调整策略与性能瓶颈排查方法,帮助开发者构建稳定高效的嵌入式实时系统。

xx56789的博客 556

opencv案例实战——银行卡模式匹配识别

在之前的几篇文章中我们已经介绍了opencv的一些基础知识,本篇文章我们将结合一个案例运用之前的知识并且学习opencv中模式匹配的应用,用到的知识点如果有不清楚的可以查看上面列出来的系列文章。具体代码会放在最后。

艾醒的博客 1339

DSP篇--C6678功能调试系列之SPI调试

SPI传输数据分为线上直接收发和通过EDMA的数据传输两种。第一种是直接将数据放在线上(SPIDAT1),在寄存器(SPIBUF)中写入命令实现数据的收发;第二种需要配置EDMA,将数据buffer直接作为EDMA的数据源进行传输,其需要配置的参数和中断与EDMA一致。

nanke_yh的博客 1258

【转】多进程那点事儿

作者:费晓行 个人空间:http://my.youkuaiyun.com/feixiaoxing 1.多线程的那点儿事(基础篇) 【 声明:版权所有,欢迎转载,请勿用于商业用途。  联系信箱:feixiaoxing @163.com】          多线程编程是现代软件技术中很重要的一个环节。要弄懂多线程,这就要牵涉到多进程?当然,要了解到多进程,就要涉及到操作系统。不

wolinxuebin的专栏 3173

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

RWK35xx命令优先级管理冲突解决策略

本文介绍在RWK35xx嵌入式平台上,通过NVIC中断优先级配置与软件命令队列结合的方式,解决多源命令冲突问题。重点实现紧急事件的低延迟响应,提升系统实时性与安全性,适用于工业控制等高可靠性场景。

weixin_42527665的博客 373

WINCC7.3破解文件

WINCC7.3硬件够破解文件,有需要的下载!WINCC7.3硬件够破解文件,有需要的下载!

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

英伟达C++ tegra面试:mutex底层原理是什么?

C++层 (std::mutex):优雅的接口,面向对象的“马甲”。glibc层 ():真正的“执行者”,管理锁的状态 (__lock, __owner),实现“用户态快速路径”(CAS)。内核层 (futex):最终的“仲裁者”,当用户态搞不定时,提供“睡眠/唤醒”机制,管理等待队列。整个过程就像一场接力赛:能抢就抢,抢到就赢!抢不到?进内核“候车室”(等待队列)睡觉!有人放锁?内核“调度员”喊醒一个“候车”的!被喊醒的线程,还得重新跑第一棒(CAS)!抢到算你狠,抢不到继续睡!

m0_60259116的博客 1047

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

左耳听风_016_15_时间管理同扭曲时间的事儿抗争

然而这并不能帮助你解决时间不够的问题啊,比如现在的很多公司啊,都会把工作安排的非常紧,今天提的需求啊恨不得明天就上线啊,比也就是为什么今天加班的严重程度啊,比我那个时候还更严重。在这里呢我觉得如果怎么做都要受伤害,那么两害相权取其精,你要学会比较是项目延期的伤害大,还是线上故障的伤害大,是先苦后甜好呢,还是积压问题好,而聪明的你啊应该能做出正确的判断。但是呢在认真思考之后呢,你觉得做不到,并且啊要给出一个你觉得你能做到的方案啊,这里的诀窍就是给出另一个你可以做到的方案,而不是把对方的方案直接回绝掉。

yyySjui的博客 94
上一篇: 提升汽车APP用户体验,火山引擎APMPlus的“独家秘笈”
下一篇: React Server Component: 混合式渲染
字节跳动终端技术
博客等级 码龄7年 206粉丝 52原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值