壹:RunLoop和线程的关系

语言: CN / TW / HK

壹:RunLoop和线程的关系

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

RunLoop

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

swift 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是一个对象,这个对象管理了需要处理的事件,并且提供了一个入口函数来处理。

swift 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()方法并不是直接调用的该方法,而是封装的以下方法:

```swift 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的部分源码:

```c 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对象。