美文网首页
iOS 主程序和主队列的区别

iOS 主程序和主队列的区别

作者: 充满活力的早晨 | 来源:发表于2018-09-20 18:12 被阅读85次

    今天看了iOS主线程和主队列的区别 的文章。这里主要涉及到两个函数,突然对这两个概念产生了兴趣,因此想验证下作者说的是否正确。想看懂别人写的代码最起码要弄懂写的啥意思吧。这里有两个函数不太懂。先研究下面这两个函数

    • void *_Nullable dispatch_get_specific(const void *key);
    • void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *_Nullable context, dispatch_function_t _Nullable destructor);

    void dispatch_queue_set_specific(dispatch_queue_t queue, const void *key, void *_Nullable context, dispatch_function_t _Nullable destructor);

    这个函数是给指定队列设置值的,我们看看具体是怎么保存值的。

    DISPATCH_NOINLINE
    void
    dispatch_queue_set_specific(dispatch_queue_t dq, const void *key,
        void *ctxt, dispatch_function_t destructor)
    {
        if (slowpath(!key)) {
            return;
        }
        dispatch_queue_specific_t dqs;
    
        dqs = _dispatch_calloc(1, sizeof(struct dispatch_queue_specific_s));
        dqs->dqs_key = key;
        dqs->dqs_ctxt = ctxt;
        dqs->dqs_destructor = destructor;
        if (slowpath(!dq->dq_specific_q)) {
            _dispatch_queue_init_specific(dq);
        }
        _dispatch_barrier_trysync_or_async_f(dq->dq_specific_q, dqs,
                _dispatch_queue_set_specific, 0);
    }
    

    上面函数_dispatch_barrier_trysync_or_async_f 最终会调用到下面函数

    static void
    _dispatch_queue_set_specific(void *ctxt)
    {
        dispatch_queue_specific_t dqs, dqsn = ctxt;
        dispatch_queue_specific_queue_t dqsq =
                (dispatch_queue_specific_queue_t)_dispatch_queue_get_current();
    
        TAILQ_FOREACH(dqs, &dqsq->dqsq_contexts, dqs_list) {
            if (dqs->dqs_key == dqsn->dqs_key) {
                // Destroy previous context for existing key
                if (dqs->dqs_destructor) {
                    dispatch_async_f(_dispatch_get_root_queue(
                            DISPATCH_QOS_DEFAULT, false), dqs->dqs_ctxt,
                            dqs->dqs_destructor);
                }
                if (dqsn->dqs_ctxt) {
                    // Copy new context for existing key
                    dqs->dqs_ctxt = dqsn->dqs_ctxt;
                    dqs->dqs_destructor = dqsn->dqs_destructor;
                } else {
                    // Remove context storage for existing key
                    TAILQ_REMOVE(&dqsq->dqsq_contexts, dqs, dqs_list);
                    free(dqs);
                }
                return free(dqsn);
            }
        }
        // Insert context storage for new key
        TAILQ_INSERT_TAIL(&dqsq->dqsq_contexts, dqsn, dqs_list);
    }
    
    image.png

    从这个函数中我们能看出具体结构的
    获取 specific queue,这相当于map 的key 值,value 是array。array 中装有dispatch_queue_specific_t 对象,我们通过判断dispatch_queue_specific_t对象的dqs_key 检查是否dispatch_queue_specific_t 对象是否相等。相等重新设置dispatch_queue_specific_t 的dqs_ctxt,不相等,直接插入到array只能中。结构如下

    image.png

    void *_Nullable dispatch_get_specific(const void *key);

    DISPATCH_NOINLINE
    void *
    dispatch_queue_get_specific(dispatch_queue_t dq, const void *key)
    {
        if (slowpath(!key)) {
            return NULL;
        }
        return _dispatch_queue_get_specific_inline(dq, key);
    }
    

    这里出现了slowpath,这个干吗用的呢?全局搜索,是宏定义,如下

    #if __GNUC__
    #define _safe_cast_to_long(x) \
            ({ _Static_assert(sizeof(typeof(x)) <= sizeof(long), \
                    "__builtin_expect doesn't support types wider than long"); \
                    (long)(x); })
    #define fastpath(x) ((typeof(x))__builtin_expect(_safe_cast_to_long(x), ~0l))
    #define slowpath(x) ((typeof(x))__builtin_expect(_safe_cast_to_long(x), 0l))
    #define likely(x) __builtin_expect(!!(x), 1)
    #define unlikely(x) __builtin_expect(!!(x), 0)
    #else
    #define fastpath(x) (x)
    #define slowpath(x) (x)
    #define likely(x) (!!(x))
    #define unlikely(x) (!!(x))
    #endif // __GNUC__
    

    这里的__builtin_expect用法,可以看我这篇文章__builtin_expect 总结

    从这个函数中调用了_dispatch_queue_get_specific_inline, 最终会调用到
    *static void _dispatch_queue_get_specific(void ctxt)

    static void
    _dispatch_queue_get_specific(void *ctxt)
    {
        void **ctxtp = ctxt;
        void *key = *ctxtp;
        dispatch_queue_specific_queue_t dqsq =
                (dispatch_queue_specific_queue_t)_dispatch_queue_get_current();
        dispatch_queue_specific_t dqs;
    
        TAILQ_FOREACH(dqs, &dqsq->dqsq_contexts, dqs_list) {
            if (dqs->dqs_key == key) {
                *ctxtp = dqs->dqs_ctxt;
                return;
            }
        }
        *ctxtp = NULL;
    }
    

    看这里就是set设置的逆运算

    DISPATCH_NOINLINE void *dispatch_queue_get_specific(dispatch_queue_t dq, const void *key) 查询的是当前queue。
    DISPATCH_NOINLINE void * dispatch_get_specific(const void *key) 查询的是指定queue。

    DISPATCH_NOINLINE
    void *
    dispatch_get_specific(const void *key)
    {
        if (slowpath(!key)) {
            return NULL;
        }
        void *ctxt = NULL;
        dispatch_queue_t dq = _dispatch_queue_get_current();
    
        while (slowpath(dq)) {
            ctxt = _dispatch_queue_get_specific_inline(dq, key);
            if (ctxt) break;
            dq = dq->do_targetq;
        }
        return ctxt;
    }
    

    验证想法
    测试代码

        static void *queueKey1 = "queueKey1";
        dispatch_queue_t queue1 = dispatch_queue_create(queueKey1, DISPATCH_QUEUE_SERIAL);
        dispatch_queue_set_specific(queue1, queueKey1, queueKey1, NULL);
        
      void * value =   dispatch_get_specific(queueKey1);
        NSLog(@"current queue %s" ,value);
        value= dispatch_queue_get_specific(queue1, queueKey1);
        NSLog(@"指定 queue %s" ,value);
      dispatch_queue_t mainQueue = dispatch_get_main_queue();
        
        dispatch_queue_set_specific(mainQueue, queueKey1, queueKey1, NULL);
        value =   dispatch_get_specific(queueKey1);
        NSLog(@"current queue %s" ,value);
    

    测试结果

    2018-09-19 14:46:55.122522+0800 PopPlayground[47991:16410734] current queue (null)
    2018-09-19 14:46:55.122678+0800 PopPlayground[47991:16410734] 指定 queue queueKey1
    2018-09-19 14:46:55.122810+0800 PopPlayground[47991:16410734] current queue queueKey1
    
    

    上面知识了解后我们开始看上篇博客哥们说的问题

    第一题(主线程只会执行主队列的任务吗?)

    从这个题,我们能看出来,我们需要主线程和两个队列(主队列和别的队列)
    测试代码

    int main(int argc, const char * argv[]) {
        
        
        const void * key = "queueKey";
        const void * mainValue = "mainValue";
        const void * serierValue = "serierValue";
        char * value ;
        dispatch_queue_t  mainQueue =    dispatch_get_main_queue();
        dispatch_queue_set_specific(mainQueue, key, mainValue, NULL);
        value = dispatch_queue_get_specific(mainQueue, key);
        NSLog(@"main queue value %s",value);
        dispatch_queue_t serier = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        dispatch_queue_set_specific(serier, key, serierValue, NULL);
        value = dispatch_queue_get_specific(serier, key);
        NSLog(@"serier queue value %s",value);
        NSLog(@"current thread %@",NSThread.currentThread);
        
        dispatch_sync(serier, ^{
            NSLog(@"serier queue sync(同步) 执行:  current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        dispatch_async(serier, ^{
            NSLog(@"serier queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
            
        });
        dispatch_async(mainQueue, ^{
            NSLog(@"main queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        
        //    dispatch_async(mainQueue, ^{
        //           NSLog(@"mian queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        //    });
        dispatch_main();
        
        return 0;
    }
    

    测试结果

    2018-09-20 15:16:16.045541+0800 TestCommand[90135:17393697] main queue value mainValue
    2018-09-20 15:16:16.046208+0800 TestCommand[90135:17393697] serier queue value serierValue
    2018-09-20 15:16:16.046618+0800 TestCommand[90135:17393697] current thread <NSThread: 0x100703530>{number = 1, name = main}
    2018-09-20 15:16:16.046713+0800 TestCommand[90135:17393697] serier queue sync(同步) 执行:  current thread <NSThread: 0x100703530>{number = 1, name = main} current queue serierValue
    2018-09-20 15:16:16.046912+0800 TestCommand[90135:17393751] serier queue async(异步) 执行:current thread <NSThread: 0x100500cb0>{number = 2, name = (null)} current queue serierValue
    2018-09-20 15:16:16.046936+0800 TestCommand[90135:17393753] main queue async(异步) 执行:current thread <NSThread: 0x10041be00>{number = 3, name = (null)} current queue mainValue
    
    

    从测试结果我们能看出来

    1 serier queue sync(同步) 执行: current thread <NSThread: 0x1006036b0>{number = 1, name = main} current queue serierValue,结果代表主线程可以执行其他队列的任务。前提是在主线程的同步执行其他队列的任务。
    2 serier queue async(异步) 执行:current thread <NSThread: 0x1028226e0>{number = 2, name = (null)} current queue serierValue ,发现异步执行serier queue ,serier会自动给我们生成一条NSTread,在该NSThread上执行 该queue。
    3 在主线程中同步执行mainQueue(主队列)的任务,发生死锁。
    4在主线程中可以异步执行mainQueue的任务。

    第二题(主队列任务只会在主线程上执行吗)

    这里我们需要主队列和 两条线程,主线程和其他线程。我们让主队列任务在其他线程中执行。

    int main(int argc, const char * argv[]) {
        
        const void * key = "queueKey";
        const void * mainValue = "mainValue";
        const void * serierValue = "serierValue";
        char * value ;
        dispatch_queue_t  mainQueue =    dispatch_get_main_queue();
        dispatch_queue_set_specific(mainQueue, key, mainValue, NULL);
        value = dispatch_queue_get_specific(mainQueue, key);
        NSLog(@"main queue value %s",value);
        dispatch_queue_t serier = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        dispatch_queue_set_specific(serier, key, serierValue, NULL);
        value = dispatch_queue_get_specific(serier, key);
        NSLog(@"serier queue value %s",value);
        NSLog(@"current thread %@",NSThread.currentThread);
        
        NSThread * tread = [[NSThread alloc]initWithBlock:^{
            NSLog(@"current thread %@",NSThread.currentThread);
            dispatch_sync(mainQueue, ^{
                NSLog(@"other thread exe main queue sync(同步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
            });
            dispatch_async(mainQueue, ^{
                NSLog(@"other thread exe main queue sync(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
            });
        }];
        
        tread.name = @"nihao";
        [tread start];
        
        dispatch_async(mainQueue, ^{
                   NSLog(@"main thead exe mian queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        dispatch_sync(serier, ^{
            NSLog(@"main thead exe serier queue sync(同步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        
        dispatch_async(serier, ^{
            NSLog(@"main thead exe serier queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
    //    dispatch_sync(mainQueue, ^{
    //        NSLog(@"ss main queue sync(同步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
    //    });
        dispatch_main();
    
        return 0;
    }
    

    执行结果

    2018-09-20 15:26:08.962266+0800 TestCommand[90753:17403728] main queue value mainValue
    2018-09-20 15:26:08.962555+0800 TestCommand[90753:17403728] serier queue value serierValue
    2018-09-20 15:26:08.962939+0800 TestCommand[90753:17403728] current thread <NSThread: 0x10050a630>{number = 1, name = main}
    2018-09-20 15:26:08.964341+0800 TestCommand[90753:17403728] main thead exe serier queue sync(同步) 执行:current thread <NSThread: 0x10050a630>{number = 1, name = main} current queue serierValue
    2018-09-20 15:26:08.964403+0800 TestCommand[90753:17403875] current thread <NSThread: 0x100457e40>{number = 2, name = nihao}
    2018-09-20 15:26:08.964456+0800 TestCommand[90753:17403873] main thead exe serier queue async(异步) 执行:current thread <NSThread: 0x10071be40>{number = 3, name = (null)} current queue serierValue
    2018-09-20 15:26:08.964506+0800 TestCommand[90753:17403872] main thead exe mian queue async(异步) 执行:current thread <NSThread: 0x100455370>{number = 4, name = (null)} current queue mainValue
    2018-09-20 15:26:08.964567+0800 TestCommand[90753:17403875] other thread exe main queue sync(同步) 执行:current thread <NSThread: 0x100457e40>{number = 2, name = nihao} current queue mainValue
    2018-09-20 15:26:08.964747+0800 TestCommand[90753:17403872] other thread exe main queue sync(异步) 执行:current thread <NSThread: 0x100455370>{number = 4, name = (null)} current queue mainValue
    

    1 从上述结果,我们发现,主线程异步 执行了main queue,mainqueue 是在新生成的thread中执行的。而不是我们想象中的,main queue 在主线程中执行。(2018-09-20 15:26:08.964506+0800 TestCommand[90753:17403872] main thead exe mian queue async(异步) 执行:current thread <NSThread: 0x100455370>{number = 4, name = (null)} current queue mainValue)
    2 主线程异步执行 serier queue ,serier queue 也是在新的thread中执行的 (2018-09-20 15:26:08.964456+0800 TestCommand[90753:17403873] main thead exe serier queue async(异步) 执行:current thread <NSThread: 0x10071be40>{number = 3, name = (null)} current queue serierValue)
    3 通过上面1 和 2 ,我们发现这里的主线程和serier 其实没有什么区别的。 那为什么和我们的经验不相符呢?(queue 任务是在主线程中执行)

    其实这都是没有上下文导致的。我们经常说main queue 在主线程中执行是因为我们的app 都已经跑起来了,苹果帮助我们已经做了好多事情,将主线程和主队列做了一个绑定关系。

    探索 main queue 与主线程的绑定

    我们将 [[NSRunLoop currentRunLoop]run]; 替换 dispatch_main();

    int main(int argc, const char * argv[]) {
        const void * key = "queueKey";
        const void * mainValue = "mainValue";
        const void * serierValue = "serierValue";
        char * value ;
        dispatch_queue_t  mainQueue =    dispatch_get_main_queue();
        dispatch_queue_set_specific(mainQueue, key, mainValue, NULL);
        value = dispatch_queue_get_specific(mainQueue, key);
        NSLog(@"main queue value %s",value);
        dispatch_queue_t serier = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
        dispatch_queue_set_specific(serier, key, serierValue, NULL);
        value = dispatch_queue_get_specific(serier, key);
        NSLog(@"serier queue value %s",value);
        NSLog(@"current thread %@",NSThread.currentThread);
        
        NSThread * tread = [[NSThread alloc]initWithBlock:^{
            NSLog(@"current thread %@",NSThread.currentThread);
            dispatch_sync(mainQueue, ^{
                NSLog(@"other thread exe main queue sync(同步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
            });
            dispatch_async(mainQueue, ^{
                NSLog(@"other thread exe main queue sync(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
            });
        }];
        
        tread.name = @"nihao";
        [tread start];
        
        dispatch_async(mainQueue, ^{
            NSLog(@"main thead exe mian queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        dispatch_sync(serier, ^{
            NSLog(@"main thead exe serier queue sync(同步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        
        dispatch_async(serier, ^{
            NSLog(@"main thead exe serier queue async(异步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        });
        //    dispatch_sync(mainQueue, ^{
        //        NSLog(@"ss main queue sync(同步) 执行:current thread %@ current queue %s",NSThread.currentThread,dispatch_get_specific(key));
        //    });
        [[NSRunLoop currentRunLoop]run];
        return 0;
    }
    

    执行结果如下

    2018-09-20 15:37:25.231760+0800 TestCommand[91209:17414161] main queue value mainValue
    2018-09-20 15:37:25.231973+0800 TestCommand[91209:17414161] serier queue value serierValue
    2018-09-20 15:37:25.232494+0800 TestCommand[91209:17414161] current thread <NSThread: 0x100703420>{number = 1, name = main}
    2018-09-20 15:37:25.233636+0800 TestCommand[91209:17414161] main thead exe serier queue sync(同步) 执行:current thread <NSThread: 0x100703420>{number = 1, name = main} current queue serierValue
    2018-09-20 15:37:25.233780+0800 TestCommand[91209:17414246] main thead exe serier queue async(异步) 执行:current thread <NSThread: 0x10078bb80>{number = 3, name = (null)} current queue serierValue
    2018-09-20 15:37:25.233805+0800 TestCommand[91209:17414254] current thread <NSThread: 0x10040fe20>{number = 2, name = nihao}
    2018-09-20 15:37:25.233910+0800 TestCommand[91209:17414161] main thead exe mian queue async(异步) 执行:current thread <NSThread: 0x100703420>{number = 1, name = main} current queue mainValue
    2018-09-20 15:37:25.233951+0800 TestCommand[91209:17414161] other thread exe main queue sync(同步) 执行:current thread <NSThread: 0x100703420>{number = 1, name = main} current queue mainValue
    2018-09-20 15:37:25.234059+0800 TestCommand[91209:17414161] other thread exe main queue sync(异步) 执行:current thread <NSThread: 0x100703420>{number = 1, name = main} current queue mainValue
    

    1 我们发现只要我们在 主线程中 运行起来runloop ,我们发现 主线程 异步 main queue 的block ,block 是在主线程中执行的(2018-09-20 15:37:25.233910+0800 TestCommand[91209:17414161] main thead exe mian queue async(异步) 执行:current thread <NSThread: 0x100703420>{number = 1, name = main} current queue mainValue)

    看到这里我们main queue ,主线程和runloop 是有关系的。

    mainqueue 的创建源码

    我们获取主队列都是通过dispatch_get_main_queue() 函数获取

    /*!
     * @function dispatch_get_main_queue
     *
     * @abstract
     * Returns the default queue that is bound to the main thread.
     *
     * @discussion
     * In order to invoke blocks submitted to the main queue, the application must
     * call dispatch_main(), NSApplicationMain(), or use a CFRunLoop on the main
     * thread.
     *
     * @result
     * Returns the main queue. This queue is created automatically on behalf of
     * the main thread before main() is called.
     */
    DISPATCH_INLINE DISPATCH_ALWAYS_INLINE DISPATCH_CONST DISPATCH_NOTHROW
    dispatch_queue_t
    dispatch_get_main_queue(void)
    {
        return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
    }
    
    #define DISPATCH_GLOBAL_OBJECT(t, x) (&(x))
    
    DISPATCH_CACHELINE_ALIGN
    struct dispatch_queue_s _dispatch_main_q = {
        DISPATCH_GLOBAL_OBJECT_HEADER(queue_main),
    #if !DISPATCH_USE_RESOLVERS
        .do_targetq = &_dispatch_root_queues[
                DISPATCH_ROOT_QUEUE_IDX_DEFAULT_QOS_OVERCOMMIT],
    #endif
        .dq_state = DISPATCH_QUEUE_STATE_INIT_VALUE(1) |
                DISPATCH_QUEUE_ROLE_BASE_ANON,
        .dq_label = "com.apple.main-thread",
        .dq_atomic_flags = DQF_THREAD_BOUND | DQF_CANNOT_TRYSYNC | DQF_WIDTH(1),
        .dq_serialnum = 1,
    };
    
    

    这里我们发现通过 dispatch_get_main_queue 获取的值就是已经定义好的全局变量 _dispatch_main_q 。 全局工程只有一份。

    CFRunloop相关代码

    CFRunloop 的创建

    static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
        CFRunLoopRef loop = NULL;
        CFRunLoopModeRef rlm;
        uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
        loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
        if (NULL == loop) {
        return NULL;
        }
        (void)__CFRunLoopPushPerRunData(loop);
        __CFRunLoopLockInit(&loop->_lock);
        loop->_wakeUpPort = __CFPortAllocate();
        if (CFPORT_NULL == loop->_wakeUpPort) HALT;
        __CFRunLoopSetIgnoreWakeUps(loop);
        loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
        loop->_commonModeItems = NULL;
        loop->_currentMode = NULL;
        loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
        loop->_blocks_head = NULL;
        loop->_blocks_tail = NULL;
        loop->_counterpart = NULL;
        loop->_pthread = t;
    #if DEPLOYMENT_TARGET_WINDOWS
        loop->_winthread = GetCurrentThreadId();
    #else
        loop->_winthread = 0;
    #endif
        rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
        if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
        return loop;
    }
    

    通过创建我们知道 runloop 和tread 是一一对应的。

    接着我们看看runloop 跑起来在干么

    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){
        uint64_t startTSR = mach_absolute_time();
    
        if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
        return kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
        rlm->_stopped = false;
        return kCFRunLoopRunStopped;
        }
        
        mach_port_name_t dispatchPort = MACH_PORT_NULL;
        Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
        if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
        
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
        mach_port_name_t modeQueuePort = MACH_PORT_NULL;
        if (rlm->_queue) {
            modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
            if (!modeQueuePort) {
                CRASH("Unable to get port for run loop mode queue (%d)", -1);
            }
        }
    #endif
        
        dispatch_source_t timeout_timer = NULL;
        struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
        if (seconds <= 0.0) { // instant timeout
            seconds = 0.0;
            timeout_context->termTSR = 0ULL;
    
        } else if (seconds <= TIMER_INTERVAL_LIMIT) {
        dispatch_queue_t queue = pthread_main_np() ? __CFDispatchQueueGetGenericMatchingMain() : __CFDispatchQueueGetGenericBackground();
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
            dispatch_retain(timeout_timer);
        timeout_context->ds = timeout_timer;
        timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
        timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
        dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
        dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
            dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
            
            uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
            ///这里超时时间是 ns_at+1 秒
            dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
            dispatch_resume(timeout_timer);
        } else { // infinite timeout
            seconds = 9999999999.0;
            timeout_context->termTSR = UINT64_MAX;
        }
    
        Boolean didDispatchPortLastTime = true;
        int32_t retVal = 0;
        do {
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
            voucher_t voucherCopy = NULL;
    #endif
            uint8_t msg_buffer[3 * 1024];
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            mach_msg_header_t *msg = NULL;
            mach_port_t livePort = MACH_PORT_NULL;
    #elif DEPLOYMENT_TARGET_WINDOWS
            HANDLE livePort = NULL;
            Boolean windowsMessageReceived = false;
    #endif
            __CFPortSet waitSet = rlm->_portSet;
            
            __CFRunLoopUnsetIgnoreWakeUps(rl);
            
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            
            ///执行下当前的rlm
            __CFRunLoopDoBlocks(rl, rlm);
            
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                ///执行source0
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            ///执行了source0 并且超时了
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    //        dispatchPort 主线程
            if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
                
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                msg = (mach_msg_header_t *)msg_buffer;
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    goto handle_msg;
                }
    #elif DEPLOYMENT_TARGET_WINDOWS
                if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
                    goto handle_msg;
                }
    #endif
            }
            
            didDispatchPortLastTime = false;
            ///没有超时,那么就进入等待之前状态
            if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
          
            ///标记为休眠状态
            __CFRunLoopSetSleeping(rl);
            // do not do any user callouts after this point (after notifying of sleeping)
            
            // Must push the local-to-this-activation ports in on every loop
            // iteration, as this mode could be run re-entrantly and we don't
            // want these ports to get serviced.
            
            __CFPortSetInsert(dispatchPort, waitSet);
            
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            
            CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
            
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
            do {
                ///等待被唤醒
                if (kCFUseCollectableAllocator) {
                    // objc_clear_stack(0);
                    // <rdar://problem/16393959>
                    memset(msg_buffer, 0, sizeof(msg_buffer));
                }
                msg = (mach_msg_header_t *)msg_buffer;
                
                ///无限暂停 相当于卡主了
                __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
               
                
                if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
    //                执行下这个队列
                    // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                    while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                    if (rlm->_timerFired) {
                        // Leave livePort as the queue port, and service timers below
                        rlm->_timerFired = false;
                        break;
                    } else {
                        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                    }
                } else {
                    // Go ahead and leave the inner loop.
                    break;
                }
            } while (1);
    #else
            if (kCFUseCollectableAllocator) {
                // objc_clear_stack(0);
                // <rdar://problem/16393959>
                memset(msg_buffer, 0, sizeof(msg_buffer));
            }
            msg = (mach_msg_header_t *)msg_buffer;
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
    #endif
            
            
    #elif DEPLOYMENT_TARGET_WINDOWS
            // Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
            __CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
    #endif
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            
            rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
            
            // Must remove the local-to-this-activation ports in on every loop
            // iteration, as this mode could be run re-entrantly and we don't
            // want these ports to get serviced. Also, we don't want them left
            // in there if this function returns.
            
            __CFPortSetRemove(dispatchPort, waitSet);
            
            __CFRunLoopSetIgnoreWakeUps(rl);
            
            // user callouts now OK again
            ///标记被唤醒
            __CFRunLoopUnsetSleeping(rl);
            if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            
            ///看看到底唤醒的是啥
            
        handle_msg:;
            __CFRunLoopSetIgnoreWakeUps(rl);
            
    #if DEPLOYMENT_TARGET_WINDOWS
            if (windowsMessageReceived) {
                // These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                
                if (rlm->_msgPump) {
                    rlm->_msgPump();
                } else {
                    MSG msg;
                    if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
                        TranslateMessage(&msg);
                        DispatchMessage(&msg);
                    }
                }
                
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                sourceHandledThisLoop = true;
                
                // To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
                // Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
                // NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
                __CFRunLoopSetSleeping(rl);
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                
                __CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
                
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                __CFRunLoopUnsetSleeping(rl);
                // If we have a new live port then it will be handled below as normal
            }
            
            
    #endif
            if (MACH_PORT_NULL == livePort) {
                CFRUNLOOP_WAKEUP_FOR_NOTHING();
                // handle nothing
            } else if (livePort == rl->_wakeUpPort) {
                CFRUNLOOP_WAKEUP_FOR_WAKEUP();
                // do nothing on Mac OS
    #if DEPLOYMENT_TARGET_WINDOWS
                // Always reset the wake up port, or risk spinning forever
                ResetEvent(rl->_wakeUpPort);
    #endif
            }
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
            else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                ///处理timer
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer, because we apparently fired early
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    #endif
    #if USE_MK_TIMER_TOO
            else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
                // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    #endif
            else if (livePort == dispatchPort) {
                CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    #if DEPLOYMENT_TARGET_WINDOWS
                void *msg = 0;
    #endif
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                sourceHandledThisLoop = true;
                didDispatchPortLastTime = true;
            } else {
                CFRUNLOOP_WAKEUP_FOR_SOURCE();
                
                // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
                voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
                
                // Despite the name, this works for windows handles as well
                CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
                if (rls) {
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
                    mach_msg_header_t *reply = NULL;
                    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                    if (NULL != reply) {
                        (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                        CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                    }
    #elif DEPLOYMENT_TARGET_WINDOWS
                    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
    #endif
                }
                
                // Restore the previous voucher
                _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
                
            }
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
    #endif
            
            __CFRunLoopDoBlocks(rl, rlm);
            
            if (sourceHandledThisLoop && stopAfterHandle) {
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout_context->termTSR < mach_absolute_time()) {
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(rl)) {
                __CFRunLoopUnsetStopped(rl);
                retVal = kCFRunLoopRunStopped;
            } else if (rlm->_stopped) {
                rlm->_stopped = false;
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
                retVal = kCFRunLoopRunFinished;
            }
    #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
            voucher_mach_msg_revert(voucherState);
            os_release(voucherCopy);
    #endif
            
        } while (0 == retVal);
    
        if (timeout_timer) {
            dispatch_source_cancel(timeout_timer);
            dispatch_release(timeout_timer);
        } else {
            free(timeout_context);
        }
    
        return retVal;
    }
    

    上面代码的逻辑图概况成这张图


    runloop run的逻辑图

    从**static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) **中我们找到这么一段代码

    #define HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY 0
    
        mach_port_name_t dispatchPort = MACH_PORT_NULL;
        Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
        if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
    #define _dispatch_get_main_queue_port_4CF _dispatch_get_main_queue_handle_4CF
    
    dispatch_runloop_handle_t
    _dispatch_get_main_queue_handle_4CF(void)
    {
        dispatch_queue_t dq = &_dispatch_main_q;
        dispatch_once_f(&_dispatch_main_q_handle_pred, dq,
                _dispatch_runloop_queue_handle_init);
        return _dispatch_runloop_queue_get_handle(dq);
    }
    
    typedef mach_port_t dispatch_runloop_handle_t;
    

    从上面我们能看出

    1 dispatchPort = _dispatch_get_main_queue_port_4CF();
    2 _dispatch_get_main_queue_port_4CF = _dispatch_get_main_queue_handle_4CF
    3 而_dispatch_get_main_queue_handle_4CF 中的返回值是从_dispatch_main_q 中获取的
    4 _dispatch_main_q 是什么呢?就是我们上面通过dispatch_get_main_queue(void) 函数获取的。

    这里主线程跑起来了,并且获取了main queue的port。获取了port,我们看看再干了啥事情。截取片段

    
               msg = (mach_msg_header_t *)msg_buffer;
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    goto handle_msg;
                }
    
     else if (livePort == dispatchPort) {
                CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    #if DEPLOYMENT_TARGET_WINDOWS
                void *msg = 0;
    #endif
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                sourceHandledThisLoop = true;
                didDispatchPortLastTime = true;
            }
    

    1 __CFRunLoopServiceMachPort 是监听端口号
    这里我们发现当端口号激活了之后,会有个检测激活的端口号是否和dispatchPort(主队列的端口号是否相同),那么主线程的runloop就处理这个端口号事件了。

    总结下

    1 没有启动主线程的runloop 的时候,主线程和其他线程没有什么区别,主队列和其他队列也没啥区别
    2 启动主线程的runloop的时候,主线程通过port 监听主队列的block,而其他线程启动runloop 没有此功能。

    这里有段话也比较重要贴下

    线程切换时的寄存器复用
    我们的代码并不是只在单线程中执行,而是可能在多个线程中执行。那么这里你就可能会产生一个疑问?既然进程中有多个线程在并行执行,而CPU中的寄存器又只有那么一套,如果不加处理岂不会产生数据错乱的场景?答案是否定的。我们知道线程是一个进程中的执行单元,每个线程的调度执行其实都是通过操作系统来完成。也就是说哪个线程占有CPU执行以及执行多久都是由操作系统控制的。具体的实现是每创建一个线程时都会为这线程创建一个数据结构来保存这个线程的信息,我们称这个数据结构为线程上下文,每个线程的上下文中有一部分数据是用来保存当前所有寄存器的副本。每当操作系统暂停一个线程时,就会将CPU中的所有寄存器的当前内容都保存到线程上下文数据结构中。而操作系统要让另外一个线程执行时则将要执行的线程的上下文中保存的所有寄存器的内容再写回到CPU中,并将要运行的线程中上次保存暂停的指令也赋值给CPU的指令寄存器,并让新线程再次执行。可以看出操作系统正是通过这种机制保证了即使是多线程运行时也不会导致寄存器的内容发生错乱的问题。因为每当线程切换时操作系统都帮它们将数据处理好了。下面的部分线程上下文结构正是指定了所有寄存器信息的部分:

    //这个结构是linux在arm32CPU上的线程上下文结构,代码来自于:http://elixir.free-electrons.com/linux/latest/source/arch/arm/include/asm/thread_info.h  
    //这里并没有保存所有的寄存器,是因为ABI中定义linux在arm上运行时所使用的寄存器并不是全体寄存器,所以只需要保存规定的寄存器的内容即可。这里并不是所有的CPU所保存的内容都是一致的,保存的内容会根据CPU架构的差异而不同。
    //因为iOS的内核并未开源所以无法得到iOS定义的线程上下文结构。
    
    //线程切换时要保存的CPU寄存器,
    struct cpu_context_save {
        __u32   r4;
        __u32   r5;
        __u32   r6;
        __u32   r7;
        __u32   r8;
        __u32   r9;
        __u32   sl;
        __u32   fp;
        __u32   sp;
        __u32   pc;
        __u32   extra[2];       /* Xscale 'acc' register, etc */
    };
    
    //线程上下文结构
    struct thread_info {
        unsigned long       flags;      /* low level flags */
        int         preempt_count;  /* 0 => preemptable, <0 => bug */
        mm_segment_t        addr_limit; /* address limit */
        struct task_struct  *task;      /* main task structure */
        __u32           cpu;        /* cpu */
        __u32           cpu_domain; /* cpu domain */
        struct cpu_context_save cpu_context;    /* cpu context */
        __u32           syscall;    /* syscall number */
        __u8            used_cp[16];    /* thread used copro */
        unsigned long       tp_value[2];    /* TLS registers */
    #ifdef CONFIG_CRUNCH
        struct crunch_state crunchstate;
    #endif
        union fp_state      fpstate __attribute__((aligned(8)));  /*浮点寄存器*/
        union vfp_state     vfpstate;  /*向量浮点寄存器*/
    #ifdef CONFIG_ARM_THUMBEE
        unsigned long       thumbee_state;  /* ThumbEE Handler Base register */
    #endif
    };
    
    
    线程切换

    libdispatch 源码地址
    cfrunloop源码地址
    cfrunloop源码下载地址
    奇怪的GCD

    相关文章

      网友评论

          本文标题:iOS 主程序和主队列的区别

          本文链接:https://www.haomeiwen.com/subject/ydwmnftx.html