内容简介:原文链接虽然自己很早前就看过RunLoop的源码,当时看得时候,有点地方还是比较生涩的。所有抽了个时间,重新整理了一下之前RunLoop的笔记。CoreFoundation源代码关于RunLoop的源码主要集中在苹果并不允许我们直接创建RunLoop,RunLoop的创建在第一次获取的时候,使用
原文链接 重拾RunLoop之源码分析1
虽然自己很早前就看过RunLoop的源码,当时看得时候,有点地方还是比较生涩的。所有抽了个时间,重新整理了一下之前RunLoop的笔记。CoreFoundation源代码关于RunLoop的源码主要集中在 CFRunLoop.c
文件中。
RunLoop的获取
苹果并不允许我们直接创建RunLoop,RunLoop的创建在第一次获取的时候,使用 [NSRunLoop mainRunLoop]
或 CFRunLoopGetMain()
可以获取主线程的RunLoop;通过 [NSRunLoop currentRunLoop]
或 CFRunLoopGetCurrent()
获取当前线程的RunLoop。
CFRunLoopGetMain()
和 CFRunLoopGetCurrent()
的源码如下:
// 主线程的RunLoop CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK();//判断是否需要fork 进程 static CFRunLoopRef __main = NULL; // no retain needed if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; } // 当前线程的RunLoop CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); //先从TSD中查找有没有相关的runloop信息,有则返回。 //我们可以理解为runloop不光存在与全局字典中,也存在中TSD中。 CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); } 复制代码
CHECK_FOR_FORK();
用来判断是否需要fork进程,这里我们可以暂时不管。
在获取主线程RunLoop的时候,它使用了 static CFRunLoopRef __main
进行保存,当第二次调用 CFRunLoopGetMain()
, __main
是有值的,就不会再重新创建,否则就使用 _CFRunLoopGet0
进行创建,传入的是 pthread_main_thread_np()
即主线程。
在获取当前线程的RunLoop的时候,首页会通过 _CFGetTSD
获取RunLoop,如果没有再通过 _CFRunLoopGet0
,传入的是当前的线程。
Thread-specific data
Thread-specific data
是线程私有数据就是上面的 TSD
,顾名思义就是存一些特定的数据的,RunLoop会保存在线程的私有数据里。
// __CFTSDTable typedef struct __CFTSDTable { uint32_t destructorCount; uintptr_t data[CF_TSD_MAX_SLOTS]; tsdDestructor destructors[CF_TSD_MAX_SLOTS]; } __CFTSDTable; // _CFGetTSD CF_EXPORT void *_CFGetTSD(uint32_t slot) { __CFTSDTable *table = __CFTSDGetTable(); if (!table) { return NULL; } uintptr_t *slots = (uintptr_t *)(table->data); return (void *)slots[slot]; } // _CFSetTSD CF_EXPORT void *_CFSetTSD(uint32_t slot, void *newVal, tsdDestructor destructor) { __CFTSDTable *table = __CFTSDGetTable(); if (!table) { return NULL; } void *oldVal = (void *)table->data[slot]; table->data[slot] = (uintptr_t)newVal; table->destructors[slot] = destructor; return oldVal; } 复制代码
__CFTSDTable
的 data
数组用来保存私有数据, destructors
数组用来保存释放函数(后面也会提到)。 destructorCount
记录 destructors
数组元素的个数。
_CFGetTSD
的作用就是获取 __CFTSDTable
的 data
数据,并返回 slot
的值。
_CFSetTSD
的作用就是给 __CFTSDTable
里设置 data[slot]
和 destructors[slot]
位置的值。
_CFRunLoopGet0
// t==0 is a synonym for "main thread" that always works // t==0是主线程的代名词 CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { // 当前线程为0,则取主线程 if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFLock(&loopsLock); // __CFRunLoops是一个全局的静态字典。 // 如果该字典为空,就进行以下两步操作 // 1.创建一个临时字典; // 2.创建主线程的RunLoop,并将它存到临时字典里 // 3.OSAtomicCompareAndSwapPtrBarrier用来将这个临时字典复制到全局字典里; // 并且使用了锁机制确保安全。 if (!__CFRunLoops) { __CFUnlock(&loopsLock); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // 当前线程RunLoop的获取,获取不到就使用__CFRunLoopCreate创建一个RunLoop,并保存在全局字典里 CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFUnlock(&loopsLock); CFRelease(newLoop); } //t为当前线程的话,将loop保存在线程私有数据中 if (pthread_equal(t, pthread_self())) { // _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); // __CFFinalizeRunLoop是RunLoop的析构函数, // PTHREAD_DESTRUCTOR_ITERATIONS 表示是线程退出时销毁线程私有数据的最大次数 // 这也是RunLoop的释放时机--线程退出的时候 if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; } 复制代码
通过源代码我们可以知道:
- RunLoop和线程之间是一一对应的,它们之间的关系保存在一个全局字典以及线程私有数据中。
- 在线程创建的时候,是没有对应的RunLoop,它的创建是在第一次获取的时候,它的销毁则发生在线程销毁的时候。
之前在看源码的时候有两个地方不是很理解。第一个就是为什么上面的loop要再取一次,在《程序员的自我修养》第29页中得到启发。里面关于单例有这样一段代码:
volatile T* pInst = 0; T* GetInstance() { if(pInst == NULL) { lock(); if(pInst == NULL) pInst = new T; unlock(); } return pInst; } 复制代码
书上只说明双重if在这里可以让lock的调用开销降到最低。为什么有这个效果,这里做一下说明。
在不考虑CPU乱序的情况下,假设有两个线程A、B同时访问 GetInstance()
,A和B同时执行第一个判断语句,结果一样,都进入了代码块。 lock()
的设定就是只允许一个线程进入,假设A先进入,B在等待。A进入后首先判断 pInst
为 NULL
,那么new一个对象,然后解锁返回对象。唤醒B,这是B进入发现第二个判断通过不了(因为 pInst
已经有值了),这样的话B就直接解锁返回对象。假设只有最外层的判断的话,那么B也会创建一个对象。
我想这里应该也是类似的作用吧。
第二个就是RunLoop销毁的时机,这个会在RunLoop的释放说明。
RunLoop的创建
使用 __CFRunLoopCreate
返回一个 CFRunLoopRef
的实例,这个函数大致分为两步:
- 使用
_CFRuntimeCreateInstance
创建一个CFRunLoopRef
实例,其实现为CFRuntime.c
文件; - 对
CFRunLoopRef
进行初始化配置,包括调用__CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
。
在 __CFRunLoopFindMode
里讲到了RunLoop的定时器,用宏进行了判断
#if DEPLOYMENT_TARGET_MACOSX #define USE_DISPATCH_SOURCE_FOR_TIMERS 1 #define USE_MK_TIMER_TOO 1 #else #define USE_DISPATCH_SOURCE_FOR_TIMERS 0 #define USE_MK_TIMER_TOO 1 #endif 复制代码
在 MACOSX
下,同时还会有使用 GCD Timer
来做定时器,而 MK_TIMER
是两个平台下都有的。
RunLoop的释放
关于RunLoop的释放是发生在线程销毁的时候。为什么这么说呢? __CFTSDGetTable()
中有一个 __CFTSDFinalize
的析构函数,其实现如下:
static void __CFTSDFinalize(void *arg) { __CFTSDSetSpecific(arg); if (!arg || arg == CF_TSD_BAD_PTR) { return; } __CFTSDTable *table = (__CFTSDTable *)arg; table->destructorCount++; for (int32_t i = 0; i < CF_TSD_MAX_SLOTS; i++) { if (table->data[i] && table->destructors[i]) { uintptr_t old = table->data[i]; table->data[i] = (uintptr_t)NULL; table->destructors[i]((void *)(old)); } } if (table->destructorCount == PTHREAD_DESTRUCTOR_ITERATIONS - 1) { // On PTHREAD_DESTRUCTOR_ITERATIONS-1 call, destroy our data free(table); __CFTSDSetSpecific(CF_TSD_BAD_PTR); return; } } 复制代码
我们可以看到, table
会循环遍历 data
和 destructors
的数据,并且把 old
变量作为 destructors
里函数的参数。所以当线程退出的时候,会调用到RunLoop的析构函数 __CFFinalizeRunLoop
释放RunLoop。
RunLoop运行
RunLoop通过 CFRunLoopRun
和 CFRunLoopRunInMode
这两个函数运行。
CFRunLoopRun
void CFRunLoopRun(void) { int32_t result; do { result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); } 复制代码
函数默认在 kCFRunLoopDefaultMode
下运行RunLoop,并且一直运行在一个do-while的循环里。 另外函数不会主动调用 CFRunLoopStop
函数( kCFRunLoopRunStopped
)或者将所有事件源移除( kCFRunLoopRunFinished
)。 从这里我们也可以了解,如果RunLoop的 _currentMode
值变化,只能退出,然后重新指定一个Mode进入。
CFRunLoopRunInMode
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } 复制代码
无论是 CFRunLoopRun
还是 CFRunLoopRunInMode
都是调用了 CFRunLoopRunSpecific
。
CFRunLoopRunSpecific
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); // 首先根据modeName找到对应Mode,如果没有则创建一个新的Mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); // 如果mode为空或者mode中没有相关的source/timer/observer,则不进入循环 if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) { Boolean did = false; if (currentMode) __CFRunLoopModeUnlock(currentMode); __CFRunLoopUnlock(rl); return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished; } volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl); CFRunLoopModeRef previousMode = rl->_currentMode; rl->_currentMode = currentMode; int32_t result = kCFRunLoopRunFinished; // 1.通知observer即将进入RunLoop if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // RunLoop真正执行的方法:第2~9步 result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // 10.通知observer已退出RunLoop if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); __CFRunLoopModeUnlock(currentMode); __CFRunLoopPopPerRunData(rl, previousPerRun); rl->_currentMode = previousMode; __CFRunLoopUnlock(rl); return result; } 复制代码
__CFRunLoopRun
__CFRunLoopRun
可以说是RunLoop运行的核心方法。由于代码过长,这里对代码进行了删减,简化后的代码如下:
/* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { // 获取CPU运行时间,用于控制超时 uint64_t startTSR = mach_absolute_time(); // 如果RunLoop或mode是stop状态,则直接return kCFRunLoopRunStopped,不进入循环 if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); return kCFRunLoopRunStopped; } else if (rlm->_stopped) { rlm->_stopped = false; return kCFRunLoopRunStopped; } // 初始化mach端口为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))); // 如果是主线程 && 传入的RunLoop是主线程的RunLoop && 传入的mode是commonMode,则给mach端口赋值为主线程收发消息的端口 if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF(); // USE_DISPATCH_SOURCE_FOR_TIMERS为1表示在MACOSX下,iOS不会调用这段代码 #if USE_DISPATCH_SOURCE_FOR_TIMERS ... #endif // GCD定时器,用于实现runloop超时机制 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; } // seconds为超时时间,超时时执行__CFRunLoopTimeout函数 else if (seconds <= TIMER_INTERVAL_LIMIT) { ... } // 永不超时 else { seconds = 9999999999.0; timeout_context->termTSR = UINT64_MAX; } // 标志位默认为true Boolean didDispatchPortLastTime = true; // 记录最后RunLoop的状态 int32_t retVal = 0; do { ... // 需要监听的端口 __CFPortSet waitSet = rlm->_portSet; // 设置RunLoop为可以被唤醒状态 __CFRunLoopUnsetIgnoreWakeUps(rl); // 2.通知observer,即将处理Timer事件 if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 3.通知observer,即将触发Source0回调 if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 执行加入当前runloop的block __CFRunLoopDoBlocks(rl, rlm); // 4.处理source0事件,有事件处理返回true,没有事件返回false Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); if (sourceHandledThisLoop) { __CFRunLoopDoBlocks(rl, rlm); } // 如果没有Sources0事件处理并且没有超时,poll为false Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR); if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI msg = (mach_msg_header_t *)msg_buffer; // 5.接收dispatchPort端口的消息,(接收source1事件)直接跳到第9步 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; // 6.通知observer,即将进入休眠 if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); // 设置RunLoop为休眠状态 __CFRunLoopSetSleeping(rl); ... #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI #if USE_DISPATCH_SOURCE_FOR_TIMERS ... #else if (kCFUseCollectableAllocator) { // objc_clear_stack(0); // <rdar://problem/16393959> memset(msg_buffer, 0, sizeof(msg_buffer)); } msg = (mach_msg_header_t *)msg_buffer; // 7.接收waitSet端口的消息,这些消息可能是 // 一个基于 port 的Source 的事件。 // 一个 Timer 到时间了 // RunLoop 自身的超时时间到了 // 被其他什么调用者手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); #endif ... // user callouts now OK again //取消runloop的休眠状态 __CFRunLoopUnsetSleeping(rl); // 8.通知observer,线程刚被唤醒 if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); //9.处理收到的消息,之后重新进入第2步 handle_msg:; ... if (MACH_PORT_NULL == livePort) { CFRUNLOOP_WAKEUP_FOR_NOTHING(); // handle nothing } else if (livePort == rl->_wakeUpPort) { CFRUNLOOP_WAKEUP_FOR_WAKEUP(); // 进入第2步重新循环 // 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 ... // 这里是GCD相关的定时器,可以忽略 #endif #if USE_MK_TIMER_TOO // 如果是定时器事件 else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); // 9.1处理timer事件 if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer __CFArmNextTimerInMode(rlm, rl); } } #endif ... CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); // 有source1事件 if (rls) { mach_msg_header_t *reply = NULL; // 9.2 处理source1事件 sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; } ... if (sourceHandledThisLoop && stopAfterHandle) { // 处理完事件就返回 retVal = kCFRunLoopRunHandledSource; } else if (timeout_context->termTSR < mach_absolute_time()) { // 超时 retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(rl)) { // RunLoop终止 __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped; } else if (rlm->_stopped) { // mode终止 rlm->_stopped = false; retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished; } ... } while (0 == retVal); ... return retVal; } 复制代码
这里盗一张RunLoop运行流程的图:
参考
程序员的自我修养深入理解RunLoop 苹果文档--RunLoop
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 以太坊源码分析(36)ethdb源码分析
- [源码分析] kubelet源码分析(一)之 NewKubeletCommand
- libmodbus源码分析(3)从机(服务端)功能源码分析
- [源码分析] nfs-client-provisioner源码分析
- [源码分析] kubelet源码分析(三)之 Pod的创建
- Spring事务源码分析专题(一)JdbcTemplate使用及源码分析
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
并行计算导论
Ananth Grama、George Karypis、张武、毛国勇、Anshul Gupta、Vipin Kumar、程海英 / 张武、毛国勇、程海英 / 机械工业出版社 / 2005-1-1 / 49.00元
《并行计算导论》(原书第2版)全面介绍并行计算的各个方面,包括体系结构、编程范例、算法与应用和标准等,涉及并行计算的新技术,也覆盖了较传统的算法,如排序、搜索、图和动态编程等。《并行计算导论》(原书第2版)尽可能采用与底层平台无关的体系结构并且针对抽象模型来设计处落地。书中选择MPI、POSIX线程和OpenMP作为编程模型,并在不同例子中反映了并行计算的不断变化的应用组合。一起来看看 《并行计算导论》 这本书的介绍吧!
XML、JSON 在线转换
在线XML、JSON转换工具
UNIX 时间戳转换
UNIX 时间戳转换