壹:RunLoop和线程的关系

壹:RunLoop和线程的关系

这里的RunLoop的源码参考了两个地址的源码,一个是GitHub上的github.com/apple/swift…中的runloop源码,一个是opensource.apple.com/source/CF/中的runloop源码,主要是以后者为准,因为后者猜测更接近为iOS上的版本,前者应该为其他平台上的实现版本。

RunLoop

RunLoop是和线程相关的基础架构的一部分。一般来说,一个线程执行完任务之后就会退出,但是这在Cocoa Touch中是行不通的,比如main thread,需要不停地去处理点击事件等等。而Runloop的目的就在于此,它让线程在有任务时工作,在无任务时休息。这种模型一般称为**Event Loop,**即事件循环,通常逻辑如下:

void CFRunLoopRun(void) {
    int result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
                                      KCFRunLoopRunLoopDefaultMode, ...);
    } while (KCFRunLoopRunStopped != result && KCFRunLoopRunFinished != result);
}
复制代码

Event Loop是在程序中等待和派发事件或者消息的一种设计模式。当Event Loop形成一个程序的中央控制流时,通常情况下,称之为主循环。Event Loop在很多程序中都有实际的应用,比如Windows程序中的消息循环,MacOS中的事件循环等等。

而在iOS中RunLoop是一个对象,这个对象管理了需要处理的事件,并且提供了一个入口函数来处理。

let runloop = RunLoop.current()
runloop.add(Port.init(), forMode: .common)
runloop.run()
复制代码

线程在执行了这个函数之后,就会一直处于“接受消息 -> 等待 -> 处理”的循环中,直到循环结束,函数返回。

在iOS中有两个类来管理RunLoop,一个是RunLoop类,一个是CFRunLoopRef。CFRunLoopRef是CoreFoundation框架封装了,提供了面向对象的API,所有这些API都是线程安全的。RunLoop是基于CFRunLoop的封装,提供了面向对象的API,但是这些API并不是线程安全的。

同时要注意RunLoop实例调用的**run()**方法并不是直接调用的该方法,而是封装的以下方法:

extension RunLoop {
	public func run() {
        while run(mode: .default, before: Date.distantFuture) { }
    }

    public func run(until limitDate: Date) {
        while run(mode: .default, before: limitDate) && limitDate.timeIntervalSinceReferenceDate > CFAbsoluteTimeGetCurrent() { }
    }

    ......
}
复制代码

RunLoop和线程

iOS中的线程,我们一般是使用Thread以及pthread_t 来管理的。通过开源的源码可以看到,**Thread是封装了pthread_t的,**而pthread_t是直接包装最底层的mach thread。同时Thread和pthread_t是一一相关的。

在Swift的Runloop中,Runloop对象是对CFRunLoop的封装,而CFRunLoop是基于pthread_t来进行管理的。

以下是RunLoop的部分源码:

typedef pthread_mutex_t CFLock_t;

/// 全局的dictionary:key是pthread_t, value是CFRunLoopRef
static CFMutableDictionaryRef loopsDic = NULL;
// 访问loopsDic的锁
static CFLock_t loopLock = (pthread_mutex_t)PTHREAD_MUTEX_INITIALIZER;

// ** 这个方法只能通过Foundation来调用
// 比如Swift的Runloop封装CFRunloop,调用了_CFRunLoopGet2这个API,这个方法内部调用了_CFRunloopGet0
// 获取一个pthread对应的Runloop
CFRunLoopRef _CFRunloopGet0(pthread_t thread) {
    // 1、首先添加互斥锁
    pthread_mutex_lock(&loopLock);

    //2、第一次进入,初始化全局Dic,并先为主线程创建一个RunLoop
    if (!loopDic) {
        loopsDic = CFDictionaryCreateMutable();
        CFRunloopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_up());
        CFDictionarySetValue(loopsDic, pthread_main_thread_up(), mainLoop);
    }

    //3、直接从全局Dic中获取
    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(loopsDic, thread);

    //4、如果取不到,那就创建一个
    if (!loop) {
        loop = __CFRunLoopCreate(thread);
        CFDictionarySetValue(loopDic, thread, loop);
     }

    //5、保证线程安全
    // 注册回调关联:当thread被销毁时,这个runloop也会被销毁!
    if (pthread_equal(t, thread_self())) {
        //TSD:Thread-share data 线程共享数据
        //这里当前thread会持有tsdTable,同时tsdtable->data[__CFTSDKeyRunLoop]的值设为loop
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);

        if (0 == _CFGetTSD(__CFTSDKeyRunloopCntr)) {
            _CFSetTSD(_CFTSDKeyRunLoopCntr, __CFFinalizeRunLoop);
        }
    }
    return loop;
}
复制代码

线程和RunLoop之间是一一对应的,保存在了一个全局的Dictionary中。线程创建的时候是没有RunLoop的,如果不主动获取,那它一直都不会有。RunLoop的创建是发生在第一次获取时,RunLoop的销毁是发生线程结束时。线程结束时会调用 __CFFinalizeRunLoop 方法,这个方法中会销毁全局的Dictionary中的Key-Value对。

除了全局的Dictionary之外,两者之间还有互相持有的关系,其一是CFRunLoop的结构体中持有pthread_t, 其二是pthread_t的TSD数据中也持有runloop。

// runLoop中持有pthread
struct __CFRunLoop {
    ...
    _CFThreadRef _pthread;
    CFMutableSetRef _modes;
    ...
}

// pthread中持有runloop
static void __CFSDSetSpecific(void *arg) {
	pthread_setspecific(_CFTSDIndexKey, arg);
}

static __CFTSDTable *__CFTSDGetTable(const Boolean create) {
    ...
    __CFTSDTable *table = (__CFTSDTable *)__CFTSDGetSpecific();
    __CFTSDSetSpecific(table);
    ...
	return table;
}

_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
复制代码

TSD

什么是pthread_t的TSD数据呢?

TSD的全称为:Thread-Specific Data即线程特有数据。TSD由具体的每一个线程来维护。TSD采用了一键多值的技术,即一个键对应多个不同的值,每个线程访问数据时通过访问该键来得到对应的数据

线程特有数据可以看作是一个二维的数组,key作为行的索引,而线程id作为列的索引。一个线程特有数据的key是一个不透明的**pthread_key_t**数据类型。在一个进程中的所有线程都可以使用这个key。即使所有的线程使用了一样的key,它们通过这个key访问到的或者修改的线程特有数据也是不同的。

Keys T1 Thread T2 Thread T3 Thread T4 Thread
K1(__CFTSDKeyRunLoop) 6 56 4 3
K2 87 21 0 9
K3 23 12 61 2
K4 11 76 47 88

以上表为例,线程T2使用的K3对应的数据是12,而线程T3使用的K3对应的数据是61。

对应到我们的代码中上述代码中的最后一行:_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); key就是_CFTSDKeyRunLoop,而value就是当前的loop。也就是说线程都会将当前的loop存储在对应的线程特有数据中。

总结

以上介绍了RunLoop和线程的关系,严格来讲它们除了互相持有之外,还有一个全局的哈希表来存储它们的对应关系,这个哈希表的Key是线程,Value是RunLoop对象。

猜你喜欢

转载自juejin.im/post/7119400023195910151
今日推荐