iOS 主线程和主队列

Posted 想名真难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS 主线程和主队列相关的知识,希望对你有一定的参考价值。

先说结论, 主队列的任务只在主线程中被执行的,而主线程运行的是一个 runloop,不仅仅只有主队列的中的任务,还会处理 UI 的布局和绘制任务, 同时还可能会有其他队列中的任务。

举个例子

问题1: 主线程是否只做主队列的事情?

- (void)someMethod 
    dispatch_queue_t queue = dispatch_queue_create("com.kk", nil);
    dispatch_sync(queue, ^
        NSLog(@"current thread = %@, curren queue = %@, main queue = %@",
              [NSThread currentThread],
              [NSString stringWithCString:dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) encoding:NSUTF8StringEncoding],
              [NSString stringWithCString:dispatch_queue_get_label(dispatch_get_main_queue()) encoding:NSUTF8StringEncoding]);
         );



------------------

current thread = <NSThread: 0x600002c0cf00>number = 1, name = main, 
curren queue = com.kk, 
main queue = com.apple.main-thread

结论: 这里是主线程但不是主队列, 主线程也做了其他队列的任务。

主线程还有可能会执行其他队列的任务。这是为了避免线程切换对性能的消耗。因为CPU的调度多线程势必会不断切换上下文。这样每个线程需要一个上下文来记录当前执行状态。这样新线程被执行时首先将上下文写入寄存器,执行结束寄存器重新写入上下文,如此不断切换才避免了多线程的数据混乱。

问题2: 主队列任务是否一定在主线程执行?

    dispatch_async(dispatch_get_global_queue(0, 0), ^
        // dispatch_sync和用dispatch_async , 一样都会回到主线程
        dispatch_sync(dispatch_get_main_queue(), ^
            NSLog(@"%@",[NSThread currentThread]);
            NSLog(@"[NSThread isMainThread] - %d",[NSThread isMainThread]);
        );
    );

------------------

//  <_NSMainThread: 0x6000008980c0>number = 1, name = main
// [NSThread isMainThread] - 1

结论:所有主队列任务只会在主线程中执行,

  • 如果在子线程调用dispatch_sync(dispatch_get_main_queue() 或者 dispatch_async(dispatch_get_main_queue() , 会回到主线程, 保证主队列的任务在主线程中执行
  • 如果在主线程使用dispatch_sync(dispatch_get_main_queue() 会死锁, block内的内容不会执行, 程序卡死
  • 如果在主线程使用dispatch_async(dispatch_get_main_queue() 在下一个主线程的runloop中执行
  • 所以结合所有情况,所有主队列的任务只会在主线程中执行。

主线程判断的几个方法

下面这几种切换到主线程执行的方法,有什么优缺点?

  • 方法1
    if ([NSThread isMainThread]) 
        //xxx
     else 
        dispatch_async(dispatch_get_main_queue(), ^
            //xxx
        );
    

使用sync/async回到主线程,大部分都是这么用的,也没有什么问题, 下面会说到一种特殊情况
  • 方法2
dispatch_async(dispatch_get_main_queue(), ^
//xxx
);

始终异步放在下一个loop使用,会延迟执行时机, 等到下一个runloop才会执行
  • 方法3
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(dispatch_get_main_queue())) 
        //xxx
     else 
        dispatch_async(dispatch_get_main_queue(), ^
            //xxx
        );
    

通过判断当前队列label的形式,可以保证任务一定是放在主队列中的。
上面已经说过, 主队列的任务一定是在主线程执行的.

综合来说,方法3是更好的判断是否是主线程的方式。

为什么推荐方法3

队列不是线程,从Apple的GCD guide中了解到,不能保证调度队列将在单独的线程上执行。 GCD将确定哪个线程,并在必要时创建一个新线程。 

查阅了sdwebimage 3.8版本和 4.4.2版本,发现了两种不同的写法

3.8版本

#define dispatch_main_sync_safe(block)\\
    if ([NSThread isMainThread]) \\
        block();\\
     else \\
        dispatch_sync(dispatch_get_main_queue(), block);\\
    

#define dispatch_main_async_safe(block)\\
    if ([NSThread isMainThread]) \\
        block();\\
     else \\
        dispatch_async(dispatch_get_main_queue(), block);\\
    

4.4.2版本

#ifndef dispatch_queue_async_safe
#define dispatch_queue_async_safe(queue, block)\\
    if (dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL) == dispatch_queue_get_label(queue)) \\
        block();\\
     else \\
        dispatch_async(queue, block);\\
    
#endif

#ifndef dispatch_main_async_safe
#define dispatch_main_async_safe(block) dispatch_queue_async_safe(dispatch_get_main_queue(), block)
#endif

那么到底上面两个版本哪个版本才是最安全的呢?

既然SDWebImage最新版本换了方式,那么肯定,4.2.2是最安全的。

那SDWebImage为什么要升级呢?

在查阅一阵子资料之后,发现在 ReactiveCocoa 的一个 issue里提到在MapKit 中的 MKMapView 有个 addOverlay 方法,这个方法不仅要在主线程中执行,而且要把这个操作加到主队列中才可以。并且后来 Apple DTS 也承认了这是一个bug

由此,我们得想一个更加安全的方式规避这种即使有此类bug,万一别的API也有这样的问题也不至于导致APP出问题.

我们知道,在主队列中的任务,一定会放到主线程执行; 所以只要是在主队列中的任务,既可以保证在主队列,也可以保证在主线程中执行。所以咱们就可以通过判断当前队列是不是主队列来代替判断当前执行任务的线程是否是主线程,这样更加安全!

所以更推荐使用方案3来作为是不是主线程的判断.

参考文章: 

主队列&主线程

在主线程执行_主线程和主队列的关系

『ios』主线程 和 主队列的关系,绝对安全的UI操作,主线程中一定是主队列?

iOS UI 操作在主线程不一定安全?

以上是关于iOS 主线程和主队列的主要内容,如果未能解决你的问题,请参考以下文章

ios/swift 调度队列 - 全局和主队列概念

在后台和主线程 ios 中执行

Redis单线程还是多线程?IO多路复用原理

python 多线程中子线程和主线程相互通信

什么是后台线程、前台线程和主线程?

OO学习总结