盒子
盒子
文章目录
  1. 解构 Run Loop
    1. Core Foundation 中 CFRunLoop 相关封装源码
      1. Run Loop 类型
      2. Run Loop Mode 类型
      3. Run Loop 源
      4. Run Loop 观察者类型
      5. Run Loop 定时器
    2. Run Loop Mode 详解
    3. Run Loop Observer
    4. Run Loop Source
    5. 开启子线程
  2. 线程概念
    1. 线程开销
    2. 创建线程
      1. NSThread/Thread
  3. 保证线程安全的方式——锁
    1. 自旋锁
    2. 信号量 dispatch_semaphore_t
    3. 互斥锁 pthread_mutex
  4. Reference

Run Loop、多线程编程及线程安全

解构 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; /* locked for accessing mode list */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
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; /* must have the run loop locked before locking this */
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; // set to true by the source when a timer has fired
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; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};

代码解释:以上代码是 Run Loop Mode 的定义,定义了 __CFRunLoopMode 类型,并定义 CFRunLoopModeRef 指针类型。其中,可以看到 Run Loop Mode 定义了 CFMutableSetRef 类型的成员变量 _sources0_sources1CFMutableArrayRef 类型的成员变量 _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; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};

上文中有看到 __CFRunLoopMode 类型存在 _sources0_sources1 两个成员变量, _sources0_sources1 都属于 Run Loop 的 Input Source。另外,可以从 __CFRunLoopSource 的结构体中看到 _context 共同体类型,存在 version0version1,而这两个变量分别指的就是当前 source 是什么类型。

void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) {    /* DOES CALLOUT */
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};
/* add new item to all common-modes */
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); // sources retain run loops!
}
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) {
// although it looses some protection for the source, we have no choice but
// to do this after unlocking the run loop and mode locks, to avoid deadlocks
// where the source wants to take a lock which is already held in another
// thread which is itself waiting for a run loop/mode lock
rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
}

代码解释:以上代码为 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; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable */
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};

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; /* immutable */
CFTimeInterval _tolerance; /* mutable */
uint64_t _fireTSR; /* TSR units */
CFIndex _order; /* immutable */
CFRunLoopTimerCallBack _callout; /* immutable */
CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

通过研读 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) {
// Perform your tasks here.
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

  • 使用 detachNewThreadSelector:toTarget:withObject: 类方法创建线程

    func createThread() {
    self.printThread(i: 251734, printRunLoop: true)
    Thread.detachNewThread {
    self.printThread(i: 200525, printRunLoop: true)
    }
    Thread.detachNewThreadSelector(#selector(detachThread), toTarget: self, with: nil)
    }
  • 使用 NSThread/Thread 创建线程对象,并调用 start 方法创建线程。

保证线程安全的方式——锁

自旋锁

信号量 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/

支持一下
扫一扫,支持forsigner
  • 微信扫一扫
  • 支付宝扫一扫