壹: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对象。
- 我正在参与技术社区创作者签约方案招募活动,点击链接报名投稿。