Run Loop、多线程编程及线程安全
2020.05.19
Niyao
 热度
℃
解构 Run Loop Run Loop 是和线程相关联的基础设施的一部分。一个 Run Loop 是一个事件处理循环,开发者能够用来处理时刻事务,以及协调接收到的事件。Run Loop 设计的目的是令程序的线程在有事务去处理师保持唤醒繁忙,在没有事务需要处理的时候使线程进入休眠。正如其名,是线程入口以及程序在做出响应时运行处理传进来的事件处理程序。程序代码提供状态控制,用于实现实际的 Run Loop 循环部分,或者说,你的代码提供 while 或者 for 循环,从而驱动 Run Loop。在你的循环中,你使用 Run Loop 对象去运行事件处理的代码,从而接收到时间,并且调用已经安装的处理程序。
Run Loop 接收两种不同类型的事件。输入源(Input Source) 传递异步事件,通常是从另一个线程或是另一个应用传递的消息。时间源(Timer Source) 传递异步事件,在一个规划的时间或重复的间隔时出现。这两种的源使用一个应用特定的处理例行程序,处理到达的事件。
Core Foundation 中 CFRunLoop 相关封装源码 NSRunLoop 的源码是闭源的,但 CFRunLoop 是开源的,可以在 https://opensource.apple.com/tarballs/CF/ 下载,任意版本都可以,我选择的是 CF-1151.16。
Run Loop 类型 typedef struct __CFRunLoop * CFRunLoopRef ;struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; __CFPort _wakeUpPort; Boolean _unused; volatile _per_run_data *_perRunData; pthread_t _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFAbsoluteTime _runTime; CFAbsoluteTime _sleepTime; CFTypeRef _counterpart; };
代码解释:以上代码是 CF 源码中 CFRunLoop 的封装,其中定义了 __CFRunLoop 的类型,并定义 CFRunLoopRef 指针类型。其中,需要注意的是,__CFRunLoop 的 _commonModes 是用于存储当前 Run Loop 的 mode,是可变的集合指针,也就是一个 Run Loop 对象对应多个 Run Loop Mode 。
Run Loop Mode 类型 typedef struct __CFRunLoopMode *CFRunLoopModeRef ;struct __CFRunLoopMode { CFRuntimeBase _base; pthread_mutex_t _lock; CFStringRef _name; Boolean _stopped; char _padding[3 ]; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; CFMutableDictionaryRef _portToV1SourceMap; __CFPortSet _portSet; CFIndex _observerMask; #if USE_DISPATCH_SOURCE_FOR_TIMERS dispatch_source_t _timerSource; dispatch_queue_t _queue; Boolean _timerFired; Boolean _dispatchTimerArmed; #endif #if USE_MK_TIMER_TOO mach_port_t _timerPort; Boolean _mkTimerArmed; #endif #if DEPLOYMENT_TARGET_WINDOWS DWORD _msgQMask; void (*_msgPump)(void ); #endif uint64_t _timerSoftDeadline; uint64_t _timerHardDeadline; };
代码解释:以上代码是 Run Loop Mode 的定义,定义了 __CFRunLoopMode 类型,并定义 CFRunLoopModeRef 指针类型。其中,可以看到 Run Loop Mode 定义了 CFMutableSetRef 类型的成员变量 _sources0 和 _sources1 ,CFMutableArrayRef 类型的成员变量 _observers 和 _timers 。从而可以得出结论 一个 Run Loop Mode 对应多个 source0、source1,以及 _observers 和 _timers
Run Loop 源 typedef struct __CFRunLoopSource * CFRunLoopSourceRef ;struct __CFRunLoopSource { CFRuntimeBase _base; uint32_t _bits; pthread_mutex_t _lock; CFIndex _order; CFMutableBagRef _runLoops; union { CFRunLoopSourceContext version0; CFRunLoopSourceContext1 version1; } _context; };
上文中有看到 __CFRunLoopMode 类型存在 _sources0 和 _sources1 两个成员变量, _sources0 和 _sources1 都属于 Run Loop 的 Input Source 。另外,可以从 __CFRunLoopSource 的结构体中看到 _context 共同体类型,存在 version0 和 version1 ,而这两个变量分别指的就是当前 source 是什么类型。
void CFRunLoopAddSource (CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return ; if (!__CFIsValid(rls)) return ; Boolean doVer0Callout = false ; __CFRunLoopLock(rl); if (modeName == kCFRunLoopCommonModes) { CFSetRef set = rl->_commonModes ? CFSetCreateCopy (kCFAllocatorSystemDefault, rl->_commonModes) : NULL ; if (NULL == rl->_commonModeItems) { rl->_commonModeItems = CFSetCreateMutable (kCFAllocatorSystemDefault, 0 , &kCFTypeSetCallBacks); } CFSetAddValue (rl->_commonModeItems, rls); if (NULL != set) { CFTypeRef context[2 ] = {rl, rls}; CFSetApplyFunction (set, (__CFRunLoopAddItemToCommonModes), (void *)context); CFRelease (set); } } else { CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true ); if (NULL != rlm && NULL == rlm->_sources0) { rlm->_sources0 = CFSetCreateMutable (kCFAllocatorSystemDefault, 0 , &kCFTypeSetCallBacks); rlm->_sources1 = CFSetCreateMutable (kCFAllocatorSystemDefault, 0 , &kCFTypeSetCallBacks); rlm->_portToV1SourceMap = CFDictionaryCreateMutable (kCFAllocatorSystemDefault, 0 , NULL , NULL ); } if (NULL != rlm && !CFSetContainsValue (rlm->_sources0, rls) && !CFSetContainsValue (rlm->_sources1, rls)) { if (0 == rls->_context.version0.version) { CFSetAddValue (rlm->_sources0, rls); } else if (1 == rls->_context.version0.version) { CFSetAddValue (rlm->_sources1, rls); __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info); if (CFPORT_NULL != src_port) { CFDictionarySetValue (rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls); __CFPortSetInsert(src_port, rlm->_portSet); } } __CFRunLoopSourceLock(rls); if (NULL == rls->_runLoops) { rls->_runLoops = CFBagCreateMutable (kCFAllocatorSystemDefault, 0 , &kCFTypeBagCallBacks); } CFBagAddValue (rls->_runLoops, rl); __CFRunLoopSourceUnlock(rls); if (0 == rls->_context.version0.version) { if (NULL != rls->_context.version0.schedule) { doVer0Callout = true ; } } } if (NULL != rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); if (doVer0Callout) { rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); } }
代码解释:以上代码为 CFRunLoopAddSource 函数,即向 Run Loop 中加入新的 Source 的方法。其中,rl 为被添加的 Run Loop, rlm 为 Run Loop Mode,rls 为 Run Loop Source。代码先判断 rlm 是否是 kCFRunLoopCommonModes 类型,如果不是则再校验 rlm 是否为空,以及新添加的 rls 是否已存在 source0 和 source1 中。如果 rls 是新的输入源,则判断 rls 的 version,当 0 == rls->_context.version0.version 时,将 rls 赋值给 rl 的 source0;当 1 == rls->_context.version0.version 时,则赋值给 source1。其中可以看到 __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info); 这句代码,也就是说 source1 是基于端口的输入源,而与之对应的 source0 是非基于端口的 。
Run Loop 观察者类型 typedef struct __CFRunLoopObserver * CFRunLoopObserverRef ;struct __CFRunLoopObserver { CFRuntimeBase _base; pthread_mutex_t _lock; CFRunLoopRef _runLoop; CFIndex _rlCount; CFOptionFlags _activities; CFIndex _order; CFRunLoopObserverCallBack _callout; CFRunLoopObserverContext _context; };
Run Loop 定时器 typedef struct CF_BRIDGED_MUTABLE_TYPE (NSTimer ) __CFRunLoopTimer * CFRunLoopTimerRef ;struct __CFRunLoopTimer { CFRuntimeBase _base; uint16_t _bits; pthread_mutex_t _lock; CFRunLoopRef _runLoop; CFMutableSetRef _rlModes; CFAbsoluteTime _nextFireDate; CFTimeInterval _interval; CFTimeInterval _tolerance; uint64_t _fireTSR; CFIndex _order; CFRunLoopTimerCallBack _callout; CFRunLoopTimerContext _context; };
通过研读 Core Foundation 中 RunLoop 相关源码,可以知道 CFRunLoopRef 是 __CFRunLoop 对外暴露的指针类型,其中 __CFRunLoop 结构中定义了pthread_t 类型的 _pthread , 以及集合(Set)存储 CFRunLoopModeRef 类型的成员变量 _modes ,记录 RunLoop Mode。而 CFRunLoopModeRef 是 __CFRunLoopMode 暴露的指针类型,其中 __CFRunLoopMode 结构中又定义了 _sources0 、_sources1 、_observers 及 _timers 四个成员变量,分别对应的类型为 CFRunLoopSourceRef CFRunLoopSourceRef CFRunLoopObserverRef 。
综上,由 RunLoop 源码封装可以得到结论,RunLoop 和 thread 的对应关系是一对一,RunLoop 可以有多个 mode,每个 mode 有多个 source 、observer 和 timer。从而,得到如下关系图。
Run Loop Mode 详解 Run Loop Observer Run Loop Source 开启子线程 利用 GCD 创建队列时,GCD 的线程池会根据队列的类型(Serial Queue, Concurrent Queue)为队列创建分配线程,对于 iOS 中创建线程会默认创建其 RunLoop 。
Your application neither creates or explicitly manages RunLoop objects. Each Thread object—including the application’s main thread—has an RunLoop object automatically created for it as needed.https://developer.apple.com/documentation/foundation/runloop
Your application does not need to create these objects explicitly; each thread, including the application’s main thread, has an associated run loop object.https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW1
- (void )createQueue { _renderQueue = dispatch_queue_create("nycode.opengles.render.queue" , DISPATCH_QUEUE_SERIAL); dispatch_set_target_queue(_renderQueue, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0 )); } - (void )addRunLoopObserver { dispatch_async (_renderQueue, ^{ [self printThread]; [self createRunloopObserver]; NSLog (@"=======================" ); [self printThread]; }); } - (void )createRunloopObserver { @autoreleasepool { CFRunLoopRef myRunLoopRef = CFRunLoopGetCurrent (); CFRunLoopObserverContext context = {0 , (__bridge void *)(self ), NULL , NULL , NULL }; CFRunLoopObserverRef observer = CFRunLoopObserverCreate (kCFAllocatorDefault,kCFRunLoopAllActivities, YES , 0 , &MyRunLoopObserver, &context); if (observer) { CFRunLoopRef cfLoop = myRunLoopRef; CFRunLoopAddObserver (cfLoop, observer, kCFRunLoopDefaultMode); } NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector (refreshModel) userInfo:nil repeats:YES ]; NSInteger loopCount = 3 ; do { [[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:5 ]]; loopCount--; NSLog (@"LoopCount : %lu" , loopCount); } while (loopCount); [timer invalidate]; timer = nil ; printf("myRunLoopRef: %p\n" , myRunLoopRef); printf("CFRunLoopGetCurrent: %p\n" , CFRunLoopGetCurrent ()); CFRunLoopStop (myRunLoopRef); printf("timer: %p\n" , timer); } } void MyRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void * info) { switch (activity) { case kCFRunLoopEntry: NSLog (@"RunLoop 进入" ); break ; case kCFRunLoopBeforeTimers: NSLog (@"即将处理 Timer" ); break ; case kCFRunLoopBeforeSources: NSLog (@"即将处理 Input Sources" ); break ; case kCFRunLoopBeforeWaiting: NSLog (@"即将睡眠" ); break ; case kCFRunLoopAfterWaiting: NSLog (@"从睡眠中唤醒,处理完唤醒源之前" ); break ; case kCFRunLoopExit: NSLog (@"退出" ); break ; default : break ; } }
源码解释:上述代码 createQueue 方法创建了一个 GCD 队列,addRunLoopObserver 为该队列所分配的线程对应子 RunLoop 添加了观察者(CFRunLoopObserverRef)和 timer(NSTimer),然后开启子线程 RunLoop 的运行循环。其中,timer 的 schedule 事务和 observer 的观察者的 handler 输出都会在该队列的子线程中执行。loopCount 控制 RunLoop 的循环次数,当循环次数为零的时候,将不再开启子线程的 RunLoop。所以,在 loopCount 尚未置为 0 之前,如果退出当前页面,当前页面持有队列,那么当前页面并不不会释放,只有等到 RunLoop 停止之后才会被释放。所以,RunLoop 的使用要谨慎,否则会造成内存泄漏。
- (void )startTimer { __weak typeof (self ) weakSelf = self ; dispatch_async (_renderQueue, ^{ [NSRunLoop .currentRunLoop addTimer:weakSelf.timer forMode:NSDefaultRunLoopMode ]; [NSRunLoop .currentRunLoop run]; [weakSelf.timer fire]; [weakSelf printThread]; }); } - (void )stopTimer { __weak typeof (self ) weakSelf = self ; dispatch_async (_renderQueue, ^{ CFRunLoopStop ((__bridge CFRunLoopRef )weakSelf.customRunLoop ); if (weakSelf.timer.isValid) { [weakSelf.timer invalidate]; weakSelf.timer = nil ; } }); }
线程概念 线程开销 创建线程 NSThread/Thread
保证线程安全的方式——锁 自旋锁 信号量 dispatch_semaphore_t 互斥锁 pthread_mutex Reference https://developer.apple.com/videos/play/wwdc2015/718/ https://developer.apple.com/videos/play/wwdc2019/258/ https://developer.apple.com/search/?q=Programming%20Guide https://developer.apple.com/library/content/documentation/iPhone/Conceptual/iPhoneOSProgrammingGuide/Introduction/Introduction.html 【iOS13 启用 UIScene 后已废弃】https://developer.apple.com/documentation/uikit/app_and_environment/managing_your_app_s_life_cycle https://www.objc.io/issues/2-concurrency/thread-safe-class-design/