线程的状态与生命周期
下图是线程状态示意图,从图中可以看出线程的生命周期是:新建 - 就绪 - 运行 - 阻塞 - 死亡
-
下面分别阐述线程生命周期中的每一步
-
新建:实例化线程对象
-
就绪:向线程对象发送start消息,线程对象被加入可调度线程池等待CPU调度。
-
运行:CPU 负责调度可调度线程池中线程的执行。线程执行完成之前,状态可能会在就绪和运行之间来回切换。就绪和运行之间的状态变化由CPU负责,程序员不能干预。
-
阻塞:当满足某个预定条件时,可以使用休眠或锁,阻塞线程执行。sleepForTimeInterval(休眠指定时长),sleepUntilDate(休眠到指定日期),@synchronized(互斥锁)。
-
死亡:正常死亡,线程执行完毕。非正常死亡,当满足某个条件后,在线程内部中止执行/在主线程中止线程对象
-
还有线程的exit和cancel
-
[NSThread exit]:一旦强行终止线程,后续的所有代码都不会被执行。
-
[thread cancel]取消:并不会直接取消线程,只是给线程对象添加 isCancelled 标记。
多线程的使用
NSThread的使用
NSThread有三种创建方式
-
init方式
-
detachNewThreadSelector创建好之后自动启动
-
performSelectorInBackground创建好之后也是直接启动
/** 方法一,需要start */ NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething1:) object:@"NSThread1"]; // 线程加入线程池等待CPU调度,时间很快,几乎是立刻执行 [thread1 start]; /** 方法二,创建好之后自动启动 */ [NSThread detachNewThreadSelector:@selector(doSomething2:) toTarget:self withObject:@"NSThread2"]; /** 方法三,隐式创建,直接启动 */ [self performSelectorInBackground:@selector(doSomething3:) withObject:@"NSThread3"]; - (void)doSomething1:(NSObject *)object { // 传递过来的参数 NSLog(@"%@",object); NSLog(@"doSomething1:%@",[NSThread currentThread]); } - (void)doSomething2:(NSObject *)object { NSLog(@"%@",object); NSLog(@"doSomething2:%@",[NSThread currentThread]); } - (void)doSomething3:(NSObject *)object { NSLog(@"%@",object); NSLog(@"doSomething3:%@",[NSThread currentThread]); } -
创建常驻线程,主线程为什么不会退出的原因
AFNetworking源码学习+ (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; } + (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; //暂停线程使用[NSRunloop currentRunloop] removePort: <#(nonnull NSPort)#> forMode: <#(nonull NSRunLoopMode)#> [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } }
GCD的使用
GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程等),底层是个线程池,线程数量有限。
-
dispatch_get_global_queue参数详解
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)//flags保留参数,不要填0以外的其他数字 -
dispatch_barrier_async栅栏,无锁队列实现(多读单写),将栅栏前后的任务分成前后两组,第一组执行完之后,才执行第二组,只能用在自己创建的对象,对于dispatch_get_global_queue,仅仅相当于调用dispatch_async
-
栅栏代码实战
- (IBAction)barrierGCD:(id)sender { // 并发队列 dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT); // 异步执行 dispatch_async(queue, ^{ for (int i = 0; i < 3; i++) { NSLog(@"栅栏:并发异步1 %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (int i = 0; i < 3; i++) { NSLog(@"栅栏:并发异步2 %@",[NSThread currentThread]); } }); dispatch_barrier_async(queue, ^{ NSLog(@"------------barrier------------%@", [NSThread currentThread]); NSLog(@"******* 并发异步执行,但是34一定在12后面 *********"); }); dispatch_async(queue, ^{ for (int i = 0; i < 3; i++) { NSLog(@"栅栏:并发异步3 %@",[NSThread currentThread]); } }); dispatch_async(queue, ^{ for (int i = 0; i < 3; i++) { NSLog(@"栅栏:并发异步4 %@",[NSThread currentThread]); } }); } -
结果输出
栅栏:并发异步1 {number = 3, name = (null)} 栅栏:并发异步2 {number = 6, name = (null)} 栅栏:并发异步1 {number = 3, name = (null)} 栅栏:并发异步2 {number = 6, name = (null)} 栅栏:并发异步1 {number = 3, name = (null)} 栅栏:并发异步2 {number = 6, name = (null)} ------------barrier------------{number = 6, name = (null)} ******* 并发异步执行,但是34一定在12后面 ********* 栅栏:并发异步4 {number = 3, name = (null)} 栅栏:并发异步3 {number = 6, name = (null)} 栅栏:并发异步4 {number = 3, name = (null)} 栅栏:并发异步3 {number = 6, name = (null)} 栅栏:并发异步4 {number = 3, name = (null)} 栅栏:并发异步3 {number = 6, name = (null)} -
GCD队列组实现
在所有队列执行完毕之后得到通知- (void)testGroup { dispatch_group_t group = dispatch_group_create(); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"队列组:有一个耗时操作完成!"); }); dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ NSLog(@"队列组:有一个耗时操作完成!"); }); //非阻塞等待所有队列执行完毕 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"队列组:前面的耗时操作都完成了,回到主线程进行相关操作"); }); //阻塞等待,DISPATCH_TIME_FOREVER时就意味着永久等待 long timeout = dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW,1 * NSEC_PER_SEC)); if (timeout == 0) { NSLog(@"回来了"); } else { NSLog(@"等待中 -- 转菊花"); } } -
dispatch_after延迟执行,不需要创建RunLoop
两种时间延迟设置- dispatch_time(DISPATCH_TIME_NOW, 10* NSEC_PER_SEC);//系统休眠停止计时
- dispatch_walltime(DISPATCH_TIME_NOW, 10* NSEC_PER_USEC);//系统休眠不停止
-
dispatch_once_t实现单例
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"程序运行过程中我只执行了一次!"); }); -
GCD延时执行
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ // 5秒后异步执行 NSLog(@"我已经等待了5秒!"); }); -
RunLoop
GCD创建的线程,默认没有开启RunLoop,如调用performSelector不会执行(performSelector实现是创建了一个NSTimer),不建议在dispatch_get_global_queue中创建RunLoop使得线程常驻
NSOperation的使用
基于GCD的更高一层的封装
-
NSOperation的三种创建方式
-
NSInvocationOperation的使用
程序在主线程执行,没有开启新线程。这是因为NSOperation多线程的使用需要配合队列NSOperationQueue- (void)testNSInvocationOperation { // 创建NSInvocationOperation NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperation) object:nil]; // 开始执行操作 [invocationOperation start]; } - (void)invocationOperation { NSLog(@"NSInvocationOperation包含的任务,没有加入队列========%@", [NSThread currentThread]); } -
NSBlockOperation的使用
程序在主线程执行,没有开启新线程。这是因为NSOperation多线程的使用需要配合队列NSOperationQueue- (void)testNSBlockOperation { // 把任务放到block中 NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"NSBlockOperation包含的任务,没有加入队列========%@", [NSThread currentThread]); }]; [blockOperation start]; } -
运用继承自NSOperation的子类
定义一个继承自NSOperation的类,然后重写它的main方法/*******************"WHOperation.h"*************************/ #import @interface WHOperation : NSOperation @end /*******************"WHOperation.m"*************************/ #import "WHOperation.h" @implementation WHOperation - (void)main { for (int i = 0; i < 3; i++) { NSLog(@"NSOperation的子类WHOperation======%@",[NSThread currentThread]); } } @end /*****************回到主控制器使用WHOperation**********************/ - (void)testWHOperation { WHOperation *operation = [[WHOperation alloc] init]; [operation start]; } -
NSOperation+NSOperationQueue,将任务加入队列执行
- (void)testOperationQueue { // 创建队列,默认并发 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 创建操作,NSInvocationOperation NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationAddOperation) object:nil]; // 创建操作,NSBlockOperation NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 3; i++) { NSLog(@"addOperation把任务添加到队列======%@", [NSThread currentThread]); } }]; [queue addOperation:invocationOperation]; [queue addOperation:blockOperation]; } - (void)invocationOperationAddOperation { NSLog(@"invocationOperation===aaddOperation把任务添加到队列====%@", [NSThread currentThread]); } -
setSuspended/cancel/cancelAllOperations
暂停和取消不是立刻取消当前操作,而是等当前的操作执行完之后不再进行新的操作。 -
操作依赖,当OperationA结束的时候,才开始执行OperationB
-
操作依赖实现代码
- (void)testAddDependency { // 并发队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 操作1 NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 3; i++) { NSLog(@"operation1======%@", [NSThread currentThread]); } }]; // 操作2 NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"****operation2依赖于operation1,只有当operation1执行完毕,operation2才会执行****"); for (int i = 0; i < 3; i++) { NSLog(@"operation2======%@", [NSThread currentThread]); } }]; // 使操作2依赖于操作1 [operation2 addDependency:operation1]; // 把操作加入队列 [queue addOperation:operation1]; [queue addOperation:operation2]; }
锁概念介绍
锁用于解决线程争夺资源的问题,一般分为两种,自旋锁(spin)和互斥锁(mutex)。
-
互斥锁可以解释为线程获取锁,发现锁被占用,就向系统申请锁空闲时唤醒他并立刻休眠。
-
自旋锁比较简单,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。
-
自旋锁应用场景
OC中实例变量的set/get,原子操作的颗粒度最小,只限于读写,对于性能的要求很高,如果使用了互斥锁势必在切换线程上耗费大量资源。相比之下,由于读写操作耗时比较小,能够在一个时间片内完成,自旋更适合这个场景。 -
自旋锁的坑
新版iOS中,系统维护了5个不同的线程优先级/QoS: background,utility,default,user-initiated,user-interactive。高优先级线程始终会在低优先级线程前执行,一个线程不会受到比它更低优先级线程的干扰。这种线程调度算法会产生潜在的优先级反转问题,导致低优先级线程迟迟不能处理资源并释放锁,导致陷入死锁,从而破坏了spin lock。 -
下面是苹果spinlock_t实现,系统中自旋锁已经全部改为互斥锁(os_unfair_lock)实现了,只是名称一直没有更改,坑货。
using spinlock_t = mutex_tt<LOCKDEBUG>; using mutex_t = mutex_tt<LOCKDEBUG>; using monitor_t = monitor_tt<LOCKDEBUG>; using recursive_mutex_t = recursive_mutex_tt<LOCKDEBUG>; // Use fork_unsafe_lock to get a lock that isn't // acquired and released around fork(). // All fork-safe locks are checked in debug builds. struct fork_unsafe_lock_t { constexpr fork_unsafe_lock_t() = default; }; extern const fork_unsafe_lock_t fork_unsafe_lock; #include "objc-lockdebug.h" template <bool Debug> class mutex_tt : nocopy_t { os_unfair_lock mLock; public: constexpr mutex_tt() : mLock(OS_UNFAIR_LOCK_INIT) { lockdebug_remember_mutex(this); } constexpr mutex_tt(const fork_unsafe_lock_t unsafe) : mLock(OS_UNFAIR_LOCK_INIT) { } void lock() { lockdebug_mutex_lock(this); os_unfair_lock_lock_with_options_inline(&mLock, OS_UNFAIR_LOCK_DATA_SYNCHRONIZATION); } void unlock() { lockdebug_mutex_unlock(this); os_unfair_lock_unlock_inline(&mLock); } }
MacOS开发中用到的锁
互斥锁
atomic
-
reallySetProperty方法实现
从源码可以看到,atomic实现是spinlock_t,而spinlock_t从上文看到内部实现是互斥锁static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy) { if (offset == 0) { object_setClass(self, newValue); return; } id oldValue; id *slot = (id*) ((char*)self + offset); if (copy) { newValue = [newValue copyWithZone:nil]; } else if (mutableCopy) { newValue = [newValue mutableCopyWithZone:nil]; } else { if (*slot == newValue) return; newValue = objc_retain(newValue); } if (!atomic) { oldValue = *slot; *slot = newValue; } else { spinlock_t& slotlock = PropertyLocks[slot]; slotlock.lock(); oldValue = *slot; *slot = newValue; slotlock.unlock(); } objc_release(oldValue); }
synchronized
-
锁实现代码
从源码可以看到,传入nil,不会上锁,会以传入的obj的地址为key,绑定一个互斥锁,当obj改变时,互斥锁失效// Begin synchronizing on 'obj'. // Allocates recursive mutex associated with 'obj' if needed. // Returns OBJC_SYNC_SUCCESS once lock is acquired. int objc_sync_enter(id obj) { int result = OBJC_SYNC_SUCCESS; if (obj) { SyncData* data = id2data(obj, ACQUIRE); assert(data); data->mutex.lock(); } else { // @synchronized(nil) does nothing if (DebugNilSync) { _objc_inform("NIL SYNC DEBUG: @synchronized(nil); set a breakpoint on objc_sync_nil to debug"); } objc_sync_nil(); } return result; } -
@synchronized(self)的坑
加锁对象对外可见(self),被外部使用@synchronized (objectA)导致死锁(class A,class B分别得到了一个锁),正确的做法是传入一个类内部维护的NSObject对象,而且这个对象是对外不可见的。//class A @synchronized (self) { [_sharedLock lock]; NSLog(@"code in class A"); [_sharedLock unlock]; } //class B [_sharedLock lock]; @synchronized (objectA) { NSLog(@"code in class B"); } [_sharedLock unlock];
信号量
互斥量值只能为0/1,信号量值可以为非负整数。 互斥量的加锁和解锁必须由同一线程分别对应使用,信号量可以由一个线程释放,另一个线程得到
- 信号量使用代码
dispatch_queue_t workConcurrentQueue = dispatch_queue_create("cccccccc", DISPATCH_QUEUE_CONCURRENT); dispatch_queue_t serialQueue = dispatch_queue_create("sssssssss",DISPATCH_QUEUE_SERIAL); dispatch_semaphore_t semaphore = dispatch_semaphore_create(3);//信号量初始为3 for (NSInteger i = 0; i < 10; i++) { dispatch_async(serialQueue, ^{ dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//将信号量减少1,信号量如果等于0,则等待 dispatch_async(workConcurrentQueue, ^{ NSLog(@"thread-info:%@开始执行任务%d",[NSThread currentThread],(int)i); sleep(1); NSLog(@"thread-info:%@结束执行任务%d",[NSThread currentThread],(int)i); dispatch_semaphore_signal(semaphore);});//将信号量增加1 }); } NSLog(@"主线程...!");