壹:RunLoop和线程的关系
壹:RunLoop和线程的关系
这里的RunLoop的源码参考了两个地址的源码,一个是GitHub上的https://github.com/apple/swift-corelibs-foundation中的runloop源码,一个是https://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对象。
- 我正在参与掘金技术社区创作者签约计划招募活动,点击链接报名投稿。