前言
- 提到线程,那就不得不提CPU,古代的CPU有一个很重要的个性,就是工夫片,每一个取得CPU的工作只能运行一个工夫片规定的工夫。
- 其实线程对操作系统来说就是一段代码以及运行时数据。操作系统会为每个线程保留相干的数据,当接管到来自CPU的工夫片中断事件时,就会按肯定规定从这些线程中抉择一个,复原它的运行时数据,这样CPU就能够继续执行这个线程了。
- 也就是其实就单核CUP而言,并没有方法实现真正意义上的并发执行,只是CPU疾速地在多条线程之间调度,CPU调度线程的工夫足够快,就造成了多线程并发执行的假象。并且就单核CPU而言多线程能够解决线程阻塞的问题,然而其自身运行效率并没有进步,多CPU的并行运算才真正解决了运行效率问题。
- 零碎中正在运行的每一个应用程序都是一个过程,每个过程零碎都会调配给它独立的内存运行。也就是说,在iOS零碎中中,每一个利用都是一个过程。
- 一个过程的所有工作都在线程中进行,因而每个过程至多要有一个线程,也就是主线程。那多线程其实就是一个过程开启多条线程,让所有工作并发执行。
- 多线程在肯定意义上实现了过程内的资源共享,以及效率的晋升。同时,在肯定水平上绝对独立,它是程序执行流的最小单元,是过程中的一个实体,是执行程序最根本的单元,有本人栈和寄存器。
明天要讲的内容
1、 iOS中的多线程
2、 iOS中的各种线程锁
3、 你不得不知的runloop
1.1 Pthreads
POSIX线程(POSIX threads),简称Pthreads,是线程的POSIX规范。该规范定义了创立和操纵线程的一整套API。在类Unix操作系统(Unix、Linux、Mac OS X等)中,都应用Pthreads作为操作系统的线程。
咱们来用Pthreads
创立一个线程去执行一个工作:
记得引入头文件`#import "pthread.h"`-(void)pthreadsDoTask{ /* pthread_t:线程指针 pthread_attr_t:线程属性 pthread_mutex_t:互斥对象 pthread_mutexattr_t:互斥属性对象 pthread_cond_t:条件变量 pthread_condattr_t:条件属性对象 pthread_key_t:线程数据键 pthread_rwlock_t:读写锁 // pthread_create():创立一个线程 pthread_exit():终止以后线程 pthread_cancel():中断另外一个线程的运行 pthread_join():阻塞以后的线程,直到另外一个线程运行完结 pthread_attr_init():初始化线程的属性 pthread_attr_setdetachstate():设置脱离状态的属性(决定这个线程在终止时是否能够被联合) pthread_attr_getdetachstate():获取脱离状态的属性 pthread_attr_destroy():删除线程的属性 pthread_kill():向线程发送一个信号 pthread_equal(): 对两个线程的线程标识号进行比拟 pthread_detach(): 拆散线程 pthread_self(): 查问线程本身线程标识号 // *创立线程 int pthread_create(pthread_t _Nullable * _Nonnull __restrict, //指向新建线程标识符的指针 const pthread_attr_t * _Nullable __restrict, //设置线程属性。默认值NULL。 void * _Nullable (* _Nonnull)(void * _Nullable), //该线程运行函数的地址 void * _Nullable __restrict); //运行函数所需的参数 *返回值: *若线程创立胜利,则返回0 *若线程创立失败,则返回出错编号 */ // pthread_t thread = NULL; NSString *params = @"Hello World"; int result = pthread_create(&thread, NULL, threadTask, (__bridge void *)(params)); result == 0 ? NSLog(@"creat thread success") : NSLog(@"creat thread failure"); //设置子线程的状态设置为detached,则该线程运行完结后会主动开释所有资源 pthread_detach(thread);}void *threadTask(void *params) { NSLog(@"%@ - %@", [NSThread currentThread], (__bridge NSString *)(params)); return NULL;}
输入后果:
ThreadDemo[1197:143578] creat thread successThreadDemo[1197:143649] <NSThread: 0x600000262e40>{number = 3, name = (null)} - Hello World
从打印后果来看,该工作是在新开拓的线程中执行的,然而感觉用起来超不敌对,很多货色须要本人治理,单单是工作队列以及线程生命周期的治理就够你头疼的,那你写出的代码还能是艺术么!其实之所以摈弃这套API很少用,是因为咱们有更好的抉择:NSThread
。
1.2 NSThread
NSThread的根本应用比较简单,能够动态创建初始化NSThread对象,对其进行设置而后启动;也能够通过NSThread的静态方法疾速创立并启动新线程;此外NSObject基类对象还提供了隐式疾速创立NSThread线程的performSelector系列类别扩大工具办法;NSThread还提供了一些动态工具接口来管制以后线程以及获取以后线程的一些信息。
咱们先来一看一下零碎提供给咱们的API天然就晓得怎么用了
@interface NSThread : NSObject//以后线程@property (class, readonly, strong) NSThread *currentThread;//应用类办法创立线程执行工作+ (void)detachNewThreadWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(nullable id)argument;//判断以后是否为多线程+ (BOOL)isMultiThreaded;//指定线程的线程参数,例如设置以后线程的断言处理器。@property (readonly, retain) NSMutableDictionary *threadDictionary;//以后线程暂停到某个工夫+ (void)sleepUntilDate:(NSDate *)date;//以后线程暂停一段时间+ (void)sleepForTimeInterval:(NSTimeInterval)ti;//退出以后线程+ (void)exit;//以后线程优先级+ (double)threadPriority;//设置以后线程优先级+ (BOOL)setThreadPriority:(double)p;//指定线程对象优先级 0.0~1.0,默认值为0.5@property double threadPriority NS_AVAILABLE(10_6, 4_0);//服务质量@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//线程名称@property (nullable, copy) NSString *name NS_AVAILABLE(10_5, 2_0);//栈区大小@property NSUInteger stackSize NS_AVAILABLE(10_5, 2_0);//是否为主线程@property (class, readonly) BOOL isMainThread NS_AVAILABLE(10_5, 2_0);//获取主线程@property (class, readonly, strong) NSThread *mainThread NS_AVAILABLE(10_5, 2_0);//初始化- (instancetype)init NS_AVAILABLE(10_5, 2_0) NS_DESIGNATED_INITIALIZER;//实例办法初始化,须要再调用start办法- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(nullable id)argument NS_AVAILABLE(10_5, 2_0);- (instancetype)initWithBlock:(void (^)(void))block API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));//线程状态,正在执行@property (readonly, getter=isExecuting) BOOL executing NS_AVAILABLE(10_5, 2_0);//线程状态,正在实现@property (readonly, getter=isFinished) BOOL finished NS_AVAILABLE(10_5, 2_0);//线程状态,曾经勾销@property (readonly, getter=isCancelled) BOOL cancelled NS_AVAILABLE(10_5, 2_0);//勾销,仅仅扭转线程状态,并不能像exist一样真正的终止线程- (void)cancel NS_AVAILABLE(10_5, 2_0);//开始- (void)start NS_AVAILABLE(10_5, 2_0);//线程须要执行的代码,个别写子类的时候会用到- (void)main NS_AVAILABLE(10_5, 2_0);@end另外,还有一个NSObject的分类,瞅一眼:@interface NSObject (NSThreadPerformAdditions)//隐式的创立并启动线程,并在指定的线程(主线程或子线程)上执行办法。- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array;- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(nullable id)arg waitUntilDone:(BOOL)wait;- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait modes:(nullable NSArray<NSString *> *)array NS_AVAILABLE(10_5, 2_0);- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(nullable id)arg waitUntilDone:(BOOL)wait NS_AVAILABLE(10_5, 2_0);- (void)performSelectorInBackground:(SEL)aSelector withObject:(nullable id)arg NS_AVAILABLE(10_5, 2_0);@end
上面以在一个UIViewController中为例展现NSThread的应用办法:
- (void)viewDidLoad { [super viewDidLoad]; /** NSThread动态工具办法 **/ /* 1 是否开启了多线程 */ BOOL isMultiThreaded = [NSThread isMultiThreaded]; /* 2 获取以后线程 */ NSThread *currentThread = [NSThread currentThread]; /* 3 获取主线程 */ NSThread *mainThread = [NSThread mainThread]; NSLog(@"main thread"); /* 4 睡眠以后线程 */ /* 4.1 线程睡眠5s钟 */ [NSThread sleepForTimeInterval:5]; /* 4.2 线程睡眠到指定工夫,成果同上 */ [NSThread sleepUntilDate:[NSDate dateWithTimeIntervalSinceNow:5]]; /* 5 退出以后线程,留神不要在主线程调用,避免主线程被kill掉 */ //[NSThread exit]; NSLog(@"main thread"); /** NSThread线程对象根本创立,target为入口函数所在的对象,selector为线程入口函数 **/ /* 1 线程实例对象创立与设置 */ NSThread *newThread= [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; /* 设置线程优先级threadPriority(0~1.0),行将被摈弃,将应用qualityOfService代替 */ newThread.threadPriority = 1.0; newThread.qualityOfService = NSQualityOfServiceUserInteractive; /* 开启线程 */ [newThread start]; /* 2 静态方法疾速创立并开启新线程 */ [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil]; [NSThread detachNewThreadWithBlock:^{ NSLog(@"block run..."); }]; /** NSObejct基类隐式创立线程的一些动态工具办法 **/ /* 1 在以后线程上执行办法,提早2s */ [self performSelector:@selector(run) withObject:nil afterDelay:2.0]; /* 2 在指定线程上执行办法,不期待以后线程 */ [self performSelector:@selector(run) onThread:newThread withObject:nil waitUntilDone:NO]; /* 3 后盾异步执行函数 */ [self performSelectorInBackground:@selector(run) withObject:nil]; /* 4 在主线程上执行函数 */ [self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:NO];}- (void)run { NSLog(@"run...");}
1.2.1 performSelecor补充
根底用法
performSelecor响应了OC语言的动态性:提早到运行时才绑定办法。当咱们在应用以下办法时:
[obj performSelector:@selector(play)];[obj performSelector:@selector(play:) withObject:@"张三"];[obj performSelector:@selector(play:with:) withObject:@"张三" withObject:@"李四"];
编译阶段并不会去查看办法是否无效存在,只会给出正告
如果要执行的办法名也是动静不确定的一个参数
编译器也只会提醒说因为以后办法名未知可能会引起内存泄露相干问题:
PerformSelector may cause a leak because its selector is unknown
提早执行
[obj performSelector:@selector(play) withObject:@"张三" afterDelay:4.f];
该办法将提早4秒后再执行play办法。其实说到对工夫方面的解决在我的项目中常常用到的是NSTimer:当一个NSTimer注册到Runloop后,Runloop会反复的在相应的工夫点注册事件,当然Runloop为了节俭资源并不会在精确的工夫点触发事件。
而performSelector:withObject:afterDelay:其实就是在外部创立了一个NSTimer,而后会增加到以后线程的Runloop中。所以当该办法增加到子线程中时,须要分外的留神两个中央:
① 在子线程中执行会不会调用test办法
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ [self performSelector:@selector(test) withObject:nil afterDelay:2];});
会发现test办法并没有被调用,因为子线程中的runloop默认是没有启动的状态。应用run办法开启以后线程的runloop,然而肯定要留神run办法和执行该提早办法的程序。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ [[NSRunLoop currentRunLoop] run]; [self performSelector:@selector(test) withObject:nil afterDelay:2];});
会发现即使增加了run办法,然而test办法还是没有被调用,在最初打印以后线程的runloop,会发现:
timers = <CFArray 0x6000002a8100 [0x109f67bb0]>{type = mutable-small, count = 1, values = ( 0 : <CFRunLoopTimer 0x6000001711c0 [0x109f67bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 544280547 (1.98647892 @ 3795501066754), callout = (Delayed Perform) lZLearningFromInterviewController test (0x105ea0d9c / 0x104b2e2c0) (), context = <CFRunLoopTimer context 0x600000470080>}
子线程的runloop中的确增加了一个CFRunLoopTimer的事件,然而到最初都不会被执行。
将run办法和performSelector提早办法调换程序后运行:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ [self performSelector:@selector(test) withObject:nil afterDelay:2]; [[NSRunLoop currentRunLoop] run];});
此时test办法会被调用,别离打印执行完performSelecor和run办法之后,发现在执行完performSelector办法后该timer事件会被增加到子线程的runloop中:
timers = <CFArray 0x6000000b3c80 [0x112956bb0]>{type = mutable-small, count = 1, values = ( 0 : <CFRunLoopTimer 0x60000016fc00 [0x112956bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 544280800 (1.98171604 @ 4048676578329), callout = (Delayed Perform) lZLearningFromInterviewController test (0x10e88fd9c / 0x1
然而当执行完run办法之后,runloop中的timer事件曾经是执行完的状态:
timers = <CFArray 0x6000000b3c80 [0x112956bb0]>{type = mutable-small, count = 0, values = ()},
所以在子线程中两者的程序必须是先执行performSelector提早办法之后再执行run办法。因为run办法只是尝试想要开启以后线程中的runloop,然而如果该线程中并没有任何事件(source、timer、observer)的话,并不会胜利的开启。
② test办法中执行的线程
[self performSelector:@selector(test) withObject:nil afterDelay:2];
如果在子线程中调用该performSelector提早办法,会发现调用该提早办法的子线程和test办法中执行的子线程是同一个,也就是说:
对于该performSelector提早办法而言,如果在主线程中调用,那么test办法也是在主线程中执行;如果是在子线程中调用,那么test也会在该子线程中执行。
在答复完提早办法之后,会将该办法和performSelector:withObject:作比照,那么performSelector:withObject:在不增加到子线程的Runloop中时是否能执行?
我过后想的是,performSelector:withObject:办法和提早办法相似,只不过是马上执行而已,所以也须要增加到子线程的RunLoop中。
这么想是错的,performSelector:withObject:只是一个单纯的音讯发送,和工夫没有一点关系。所以不须要增加到子线程的Runloop中也能执行。
所以能间接应用NSThread的三个办法:
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(test) object:nil];[NSThread detachNewThreadSelector:@selector(test) toTarget:self withObject:nil];[NSThread detachNewThreadWithBlock:^{ NSLog(@"block中的线程 ---- %@",[NSThread currentThread]);}];
performSelector如何进行多值传输?
问题一听马上就能答复应用NSArray或者NSDictionary或者自定义Model的模式,然而我查到了一个很妙的办法:
因为在OC中调用一个办法实际上就是发送音讯objc_msgSend:
{ NSNumber *age = [NSNumber numberWithInt:20]; NSString *name = @"李周"; NSString *gender = @"女"; NSArray *friends = @[@"谢华华",@"亚呼呼"]; SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:"); NSArray *array = @[age,name,gender,friends]; ((void(*)(id,SEL,NSNumber*,NSString*,NSString*,NSArray*)) objc_msgSend)(self,selector,age,name,gender,friends);}- (void)getAge:(NSNumber *)age name:(NSString *)name gender:(NSString *)gender friends:(NSArray *)friends{ NSLog(@"%d----%@---%@---%@",[age intValue],name,gender,friends[0]);}
导入#import <objc/message.h>即可。然而这种形式并不是oc封装的办法所以应用非常的不不便。
第二种办法其实也是以NSArray的模式传值,而后创立NSInvocation的形式,将参数一一绑定。
-(id)performSelector:(SEL)aSelector withObject:(NSArray *)object{ //取得办法签名 NSMethodSignature *signature = [[self class] instanceMethodSignatureForSelector:aSelector]; if (signature == nil) { return nil; } //应用NSInvocation进行参数的封装 NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; invocation.target = self; invocation.selector = aSelector; //减去 self _cmd NSInteger paramtersCount = signature.numberOfArguments - 2; paramtersCount = MIN(object.count, paramtersCount); for (int i = 0; i < paramtersCount; i++) { id obj = object[i]; if ([obj isKindOfClass:[NSNull class]]) continue; [invocation setArgument:&obj atIndex:i+2]; } [invocation invoke]; id returnValue = nil; if (signature.methodReturnLength > 0) { //如果有返回值的话,才须要去取得返回值 [invocation getReturnValue:&returnValue]; } return returnValue; } NSNumber *age = [NSNumber numberWithInt:20]; NSString *name = @"李周"; NSString *gender = @"女"; NSArray *friends = @[@"谢华华",@"亚呼呼"]; SEL selector = NSSelectorFromString(@"getAge:name:gender:friends:"); NSArray *array = @[age,name,gender,friends]; [self performSelector:selector withObject:array];
1.3 GCD
GCD,全名Grand Central Dispatch
,大中枢派发,是基于C语言的一套多线程开发API,是目前苹果官网举荐的多线程开发方式。总体来说,他解决我提到的下面间接操作线程带来的难题,它主动帮你治理了线程的生命周期以及工作的执行规定。上面咱们会频繁的说道一个词,那就是工作
,说白了,工作
其实就是你要执行的那段代码
。
工作治理形式——队列
当咱们要治理多个工作时,线程开发给咱们带来了肯定的技术难度,或者说不方便性,GCD给出了咱们对立治理工作的形式,那就是队列。咱们来看一下iOS
多线程操作中的队列:(⚠️不论是串行还是并行,队列都是依照FIFO的准则顺次触发工作)
两个通用队列:
- 串行队列:所有工作会在一条线程中执行(有可能是以后线程也有可能是新开拓的线程),并且一个工作执行结束后,才开始执行下一个工作。(期待实现)
- 并行队列:能够开启多条线程并行执行工作(但不肯定会开启新的线程),并且当一个工作放到指定线程开始执行时,下一个工作就能够开始执行了。(期待产生)
两个非凡队列:
- 主队列:零碎为咱们创立好的一个串行队列,牛逼之处在于它治理必须在主线程中执行的工作,属于有劳保的。
- 全局队列:零碎为咱们创立好的一个并行队列,应用起来与咱们本人创立的并行队列无实质差异。
工作执行形式
说完队列,相应的,工作除了治理,还得执行。并且在GCD中并不能间接开拓线程执行工作,所以在工作退出队列之后,GCD给出了两种执行形式——同步执行(sync)和异步执行(async)。
- 同步执行:在以后线程执行工作,不会开拓新的线程。必须等到Block函数执行结束后,dispatch函数才会返回。
- 异步执行:能够在新的线程中执行工作,但不肯定会开拓新的线程。dispatch函数会立刻返回, 而后Block在后盾异步执行。
工作队列组合形式
1. 线程死锁
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1========%@",[NSThread currentThread]); dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"2========%@",[NSThread currentThread]); }); NSLog(@"3========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[5615:874679] 1========<NSThread: 0x608000072440>{number = 1, name = main}
咱们还是得剖析一下为什么会死锁:
咱们先做一个定义:- (void)viewDidLoad{} ---> 工作A,GCD同步工作 --->工作B。
总而言之呢,大略是这样的,首先,工作A在主队列,并且曾经开始执行,在主线程打印出1===... ...
,而后这时工作B被退出到主队列中,并且同步执行,这尼玛事都大了,零碎说,同步执行啊,那我不开新的线程了,工作B说我要等我外面的Block函数执行实现,要不我就不返回,然而主队列说了,玩蛋去,我是串行的,你得等A执行完能力轮到你,不能坏了规矩,同时,工作B作为工作A的外部函数,必须等工作B执行完函数返回能力执行下一个工作。那就造成了,工作A期待工作B实现能力继续执行,但作为串行队列的主队列又不能让工作B在工作A未实现之前开始执行,所以工作A等着工作B实现,工作B等着工作A实现,期待,永恒的期待。所以就死锁了。
2. 这样不死锁
不如就写个最简略的:
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1========%@",[NSThread currentThread]); NSLog(@"2========%@",[NSThread currentThread]); NSLog(@"3========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[5803:939324] 1========<NSThread: 0x600000078340>{number = 1, name = main}ThreadDemo[5803:939324] 2========<NSThread: 0x600000078340>{number = 1, name = main}ThreadDemo[5803:939324] 3========<NSThread: 0x600000078340>{number = 1, name = main}
其实这里有一个误区,那就是工作在主线程程序执行就是主队列。其实一点关系都没有,如果以后在主线程,同步执行工作,不论在什么队列工作都是程序执行。把所有工作都以异步执行的形式退出到主队列中,你会发现它们也是程序执行的。
3. 咱们改一下
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1========%@",[NSThread currentThread]); dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"2========%@",[NSThread currentThread]); }); NSLog(@"3========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[5830:947858] 1========<NSThread: 0x60000007bb80>{number = 1, name = main}ThreadDemo[5830:947858] 2========<NSThread: 0x60000007bb80>{number = 1, name = main}ThreadDemo[5830:947858] 3========<NSThread: 0x60000007bb80>{number = 1, name = main}
你发现失常执行了,并且是程序执行的,和上诉状况一样,工作A在主队列中,然而工作B退出到了全局队列,这时候,工作A和工作B没有队列的束缚,所以工作B就先执行,执行结束之后函数返回,工作A接着执行。
4. 咱们再改一下
- (void)viewDidLoad { [super viewDidLoad]; NSLog(@"1========%@",[NSThread currentThread]); dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"2========%@",[NSThread currentThread]); }); NSLog(@"3========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[5911:962470] 1========<NSThread: 0x600000072700>{number = 1, name = main}ThreadDemo[5911:962470] 3========<NSThread: 0x600000072700>{number = 1, name = main}ThreadDemo[5911:962470] 2========<NSThread: 0x600000072700>{number = 1, name = main}
发现不是程序打印了,而且也不会死锁,明明都是加到主队列里了啊,其实当工作A在执行时,工作B退出到了主队列,留神,是异步执行,所以dispatch函数不会等到Block执行实现才返回,dispatch函数返回后,那工作A能够继续执行,Block工作咱们能够认为在下一帧程序退出队列,并且默认有限下一帧执行。这就是为什么你看到2===... ...
是最初输入的了。(⚠️一个函数的有多个外部函数异步执行时,不会造成死锁的同时,工作A执行结束后,这些异步执行的外部函数会程序执行)。
队列与执行形式的搭配
//串行队列self.serialQueue = dispatch_queue_create("serialQueue.ys.com", DISPATCH_QUEUE_SERIAL);//并行队列self.concurrentQueue = dispatch_queue_create("concurrentQueue.ys.com", DISPATCH_QUEUE_CONCURRENT);
1. 串行队列 + 同步执行
-(void)queue_taskTest{ dispatch_sync(self.serialQueue, ^{ NSLog(@"1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_sync(self.serialQueue, ^{ NSLog(@"2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_sync(self.serialQueue, ^{ NSLog(@"3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"4========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[6735:1064390] 1========<NSThread: 0x600000073cc0>{number = 1, name = main}ThreadDemo[6735:1064390] 2========<NSThread: 0x600000073cc0>{number = 1, name = main}ThreadDemo[6735:1064390] 3========<NSThread: 0x600000073cc0>{number = 1, name = main}ThreadDemo[6735:1064390] 4========<NSThread: 0x600000073cc0>{number = 1, name = main}
全副都在以后线程程序执行,也就是说,同步执行不具备开拓新线程的能力。
2. 串行队列 + 异步执行
-(void)queue_taskTest{ dispatch_async(self.serialQueue, ^{ NSLog(@"1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_async(self.serialQueue, ^{ NSLog(@"2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_async(self.serialQueue, ^{ NSLog(@"3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"4========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[6774:1073235] 4========<NSThread: 0x60800006e9c0>{number = 1, name = main}ThreadDemo[6774:1073290] 1========<NSThread: 0x608000077000>{number = 3, name = (null)}ThreadDemo[6774:1073290] 2========<NSThread: 0x608000077000>{number = 3, name = (null)}ThreadDemo[6774:1073290] 3========<NSThread: 0x608000077000>{number = 3, name = (null)}
先打印了4,而后程序在子线程中打印1,2,3。阐明异步执行具备开拓新线程的能力,并且串行队列必须等到前一个工作执行完能力开始执行下一个工作,同时,异步执行会使外部函数率先返回,不会与正在执行的内部函数产生死锁。
3. 并行队列 + 同步执行
-(void)queue_taskTest{ dispatch_sync(self.concurrentQueue, ^{ NSLog(@"1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_sync(self.concurrentQueue, ^{ NSLog(@"2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_sync(self.concurrentQueue, ^{ NSLog(@"3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"4========%@",[NSThread currentThread]);}
运行后果:
ThreadDemo[7012:1113594] 1========<NSThread: 0x60800007e340>{number = 1, name = main}ThreadDemo[7012:1113594] 2========<NSThread: 0x60800007e340>{number = 1, name = main}ThreadDemo[7012:1113594] 3========<NSThread: 0x60800007e340>{number = 1, name = main}ThreadDemo[7012:1113594] 4========<NSThread: 0x60800007e340>{number = 1, name = main}
未开启新的线程执行工作,并且Block函数执行实现后dispatch函数才会返回,能力持续向下执行,所以咱们看到的后果是程序打印的。
4. 并行队列 + 异步执行
-(void)queue_taskTest{ dispatch_async(self.concurrentQueue, ^{ NSLog(@"1========%@",[NSThread currentThread]); //[self nslogCount:10000 number:1]; }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"2========%@",[NSThread currentThread]); //[self nslogCount:10000 number:2]; }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"3========%@",[NSThread currentThread]); //[self nslogCount:10000 number:3]; }); NSLog(@"4========%@",[NSThread currentThread]);}
打印后果:
ThreadDemo[7042:1117492] 1========<NSThread: 0x600000071900>{number = 3, name = (null)}ThreadDemo[7042:1117491] 3========<NSThread: 0x608000070240>{number = 5, name = (null)}ThreadDemo[7042:1117451] 4========<NSThread: 0x600000067400>{number = 1, name = main}ThreadDemo[7042:1117494] 2========<NSThread: 0x600000071880>{number = 4, name = (null)}
开拓了多个线程,触发工作的机会是程序的,然而咱们看到实现工作的工夫却是随机的,这取决于CPU对于不同线程的调度调配,然而,线程不是无条件有限开拓的,当任务量足够大时,线程是会反复利用的。
总结一下
1. 对于单核CPU来说,不存在真正意义上的并行,所以,多线程执行工作,其实也只是一个人在干活,CPU的调度决定了非期待工作的执行速率,同时对于非期待工作,多线程并没有真正意义提高效率。
2. 线程能够简略的认为就是一段代码+运行时数据。
3. 同步执行会在以后线程执行工作,不具备开拓线程的能力或者说没有必要开拓新的线程。并且,同步执行必须等到Block函数执行结束,dispatch函数才会返回,从而阻塞同一串行队列中内部办法的执行。
4. 异步执行dispatch函数会间接返回,Block函数咱们能够认为它会在下一帧退出队列,并依据所在队列目前的工作状况有限下一帧执行,从而不会阻塞以后内部工作的执行。同时,只有异步执行才有开拓新线程的必要,然而异步执行不肯定会开拓新线程。
5. 只有是队列,必定是FIFO(先进先出),然而谁先执行完要看第1条。
6. 只有是串行队列,必定要等上一个工作执行实现,能力开始下一个工作。然而并行队列当上一个工作开始执行后,下一个工作就能够开始执行。
7. 想要开拓新线程必须让工作在异步执行,想要开拓多个线程,只有让工作在并行队列中异步执行才能够。执行形式和队列类型多层组合在肯定水平上可能实现对于代码执行程序的调度。
8. 同步+串行:未开拓新线程,串行执行工作;同步+并行:未开拓新线程,串行执行工作;异步+串行:新开拓一条线程,串行执行工作;异步+并行:开拓多条新线程,并行执行工作;在主线程中同步应用主队列执行工作,会造成死锁。
9. 对于多核CPU来说,线程数量也不能有限开拓,线程的开拓同样会耗费资源,过多线程同时解决工作并不是设想中的人多力量大。
比喻:
工作的治理形式——队列,串行队列和并行队列就像是人以什么规定打电话,排队一个等一个去,还是抢着去;
工作的执行形式——同步或异步执行,就像提供以后一个电话机,还是能够申请新的电话机。
而多线程的运作就等于是这些人去打电话。
同步执行的时候不能开拓新的线程,异步执行的时候能够开拓新的线程,但不肯定开拓。
GCD其余函数用法
1. dispatch_after
该函数用于工作延时执行,其中参数dispatch_time_t
代表延时时长,dispatch_queue_t
代表应用哪个队列。如果队列未主队列,那么工作在主线程执行,如果队列为全局队列或者本人创立的队列,那么工作在子线程执行,代码如下:
-(void)GCDDelay{ //主队列延时 dispatch_time_t when_main = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)); dispatch_after(when_main, dispatch_get_main_queue(), ^{ NSLog(@"main_%@",[NSThread currentThread]); }); //全局队列延时 dispatch_time_t when_global = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)); dispatch_after(when_global, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"global_%@",[NSThread currentThread]); }); //自定义队列延时 dispatch_time_t when_custom = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)); dispatch_after(when_custom, self.serialQueue, ^{ NSLog(@"custom_%@",[NSThread currentThread]); });}
打印后果:
ThreadDemo[1508:499647] main_<NSThread: 0x60000007cf40>{number = 1, name = main}ThreadDemo[1508:499697] global_<NSThread: 0x608000262d80>{number = 3, name = (null)}ThreadDemo[1508:499697] custom_<NSThread: 0x608000262d80>{number = 3, name = (null)}
2. dispatch_once
保障函数在整个生命周期内只会执行一次,看代码。
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog(@"%@",[NSThread currentThread]); });}
打印后果:
ThreadDemo[1524:509261] <NSThread: 0x600000262940>{number = 1, name = main}无论你怎么疯狂的点击,在第一次打印之后,输入台便岿然不动。
3. dispatch_group_async & dispatch_group_notify
-(void)GCDGroup{ // [self jointImageView]; // dispatch_group_t group = dispatch_group_create(); __block UIImage *image_1 = nil; __block UIImage *image_2 = nil; //在group中增加一个工作 dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ image_1 = [self imageWithPath:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1502706256731&di=371f5fd17184944d7e2b594142cd7061&imgtype=0&src=http%3A%2F%2Fimg4.duitang.com%2Fuploads%2Fitem%2F201605%2F14%2F20160514165210_LRCji.jpeg"]; }); dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ image_2 = [self imageWithPath:@"https://ss3.bdstatic.com/70cFv8Sh_Q1YnxGkpoWK1HF6hhy/it/u=776127947,2002573948&fm=26&gp=0.jpg"]; }); //group中所有工作执行结束,告诉该办法执行 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ self.imageView_1.image = image_1; self.imageView_2.image = image_2; // UIGraphicsBeginImageContextWithOptions(CGSizeMake(200, 100), NO, 0.0f); [image_2 drawInRect:CGRectMake(0, 0, 100, 100)]; [image_1 drawInRect:CGRectMake(100, 0, 100, 100)]; UIImage *image_3 = UIGraphicsGetImageFromCurrentImageContext(); self.imageView_3.image = image_3; UIGraphicsEndImageContext(); });}-(void)jointImageView{ self.imageView_1 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 50, 100, 100)]; [self.view addSubview:_imageView_1]; self.imageView_2 = [[UIImageView alloc] initWithFrame:CGRectMake(140, 50, 100, 100)]; [self.view addSubview:_imageView_2]; self.imageView_3 = [[UIImageView alloc] initWithFrame:CGRectMake(20, 200, 200, 100)]; [self.view addSubview:_imageView_3]; self.imageView_1.layer.borderColor = self.imageView_2.layer.borderColor = self.imageView_3.layer.borderColor = [UIColor grayColor].CGColor; self.imageView_1.layer.borderWidth = self.imageView_2.layer.borderWidth = self.imageView_3.layer.borderWidth = 1;}
4. dispatch_barrier_async
栅栏函数,应用此办法创立的工作,会查找以后队列中有没有其余工作要执行,如果有,则期待已有工作执行结束后再执行,同时,在此工作之后进入队列的工作,须要期待此工作执行实现后,能力执行。看代码(⚠️ 这里并发队列必须是本人创立的。如果抉择全局队列,这个函数和dispatch_async将会没有差异。)
-(void)GCDbarrier{ dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作1"); }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作2"); }); // dispatch_barrier_async(self.concurrentQueue, ^{// NSLog(@"工作barrier");// }); // NSLog(@"big"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作3"); });// NSLog(@"apple"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作4"); });}
运行后果:
ThreadDemo[1816:673351] 工作3ThreadDemo[1816:673353] 工作1ThreadDemo[1816:673350] 工作2ThreadDemo[1816:673370] 工作4
上面咱们关上第一句正文:
-(void)GCDbarrier{ dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作1"); }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作2"); }); dispatch_barrier_async(self.concurrentQueue, ^{ NSLog(@"工作barrier"); }); // NSLog(@"big"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作3"); });// NSLog(@"apple"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作4"); });}
打印后果:
ThreadDemo[1833:678739] 工作2ThreadDemo[1833:678740] 工作1ThreadDemo[1833:678740] 工作barrierThreadDemo[1833:678740] 工作3ThreadDemo[1833:678739] 工作4
再关上第二个和第三个正文,如下:
-(void)GCDbarrier{ dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作1"); }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作2"); }); dispatch_barrier_async(self.concurrentQueue, ^{ NSLog(@"工作barrier"); }); NSLog(@"big"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作3"); }); NSLog(@"apple"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作4"); });}
运行后果:
ThreadDemo[1853:692434] 工作1ThreadDemo[1853:692421] 工作2ThreadDemo[1853:692387] bigThreadDemo[1853:692421] 工作barrierThreadDemo[1853:692387] appleThreadDemo[1853:692421] 工作3ThreadDemo[1853:692434] 工作4
咱们换一下函数:
-(void)GCDbarrier{ dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作1"); }); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作2"); }); dispatch_barrier_sync(self.concurrentQueue, ^{ NSLog(@"工作barrier"); }); NSLog(@"big"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作3"); }); NSLog(@"apple"); dispatch_async(self.concurrentQueue, ^{ NSLog(@"工作4"); });}
打印后果:
ThreadDemo[1874:711841] 工作1ThreadDemo[1874:711828] 工作2ThreadDemo[1874:711793] 工作barrierThreadDemo[1874:711793] bigThreadDemo[1874:711793] appleThreadDemo[1874:711828] 工作3ThreadDemo[1874:711841] 工作4
发现了吗?这两个函数对于队列的栅栏作用是一样的,然而对于该函数绝对于其余外部函数遵循了最开始说到的同步和异步的规定。
5. dispatch_apply
该函数用于反复执行某个工作,如果工作队列是并行队列,反复执行的工作会并发执行,如果工作队列为串行队列,则工作会程序执行,须要留神的是,该函数为同步函数,要避免线程阻塞和死锁哦。
串行队列:
-(void)GCDApply{ //反复执行 dispatch_apply(5, self.serialQueue, ^(size_t i) { NSLog(@"第%@次_%@",@(i),[NSThread currentThread]); });}
运行后果:
ThreadDemo[1446:158101] 第0次_<NSThread: 0x600000079ac0>{number = 1, name = main}ThreadDemo[1446:158101] 第1次_<NSThread: 0x600000079ac0>{number = 1, name = main}ThreadDemo[1446:158101] 第2次_<NSThread: 0x600000079ac0>{number = 1, name = main}ThreadDemo[1446:158101] 第3次_<NSThread: 0x600000079ac0>{number = 1, name = main}ThreadDemo[1446:158101] 第4次_<NSThread: 0x600000079ac0>{number = 1, name = main}
并行队列:
-(void)GCDApply{ //反复执行 dispatch_apply(5, self.concurrentQueue, ^(size_t i) { NSLog(@"第%@次_%@",@(i),[NSThread currentThread]); });}
运行后果:
ThreadDemo[1461:160567] 第2次_<NSThread: 0x608000076000>{number = 4, name = (null)}ThreadDemo[1461:160534] 第0次_<NSThread: 0x60800006d8c0>{number = 1, name = main}ThreadDemo[1461:160566] 第3次_<NSThread: 0x60000007d480>{number = 5, name = (null)}ThreadDemo[1461:160569] 第1次_<NSThread: 0x60000007d440>{number = 3, name = (null)}ThreadDemo[1461:160567] 第4次_<NSThread: 0x608000076000>{number = 4, name = (null)}
死锁:
-(void)GCDApply{ //反复执行 dispatch_apply(5, dispatch_get_main_queue(), ^(size_t i) { NSLog(@"第%@次_%@",@(i),[NSThread currentThread]); });}
运行后果:
6. dispatch_semaphore_create & dispatch_semaphore_signal & dispatch_semaphore_wait
看这几个函数的时候你须要抛开队列,丢掉同步异步,不要把它们想到一起,一概而论,信号量只是管制工作执行的一个条件而已,绝对于下面通过队列以及执行形式来控制线程的开拓和工作的执行,它更贴近对于工作间接的管制。相似于单个队列的最大并发数的管制机制,进步并行效率的同时,也避免太多线程的开拓对CPU早层负面的效率累赘。dispatch_semaphore_create
创立信号量,初始值不能小于0;dispatch_semaphore_wait
期待升高信号量,也就是信号量-1;dispatch_semaphore_signal
进步信号量,也就是信号量+1;dispatch_semaphore_wait
和dispatch_semaphore_signal
通常配对应用。
-(void)GCDSemaphore{ // //dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_apply(5, self.concurrentQueue, ^(size_t i) { //dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_async(self.concurrentQueue, ^{ NSLog(@"第%@次_%@",@(i),[NSThread currentThread]); //dispatch_semaphore_signal(semaphore); }); });}
ThreadDemo[1970:506692] 第0次_<NSThread: 0x600000070f00>{number = 3, name = (null)}ThreadDemo[1970:506711] 第1次_<NSThread: 0x6000000711c0>{number = 4, name = (null)}ThreadDemo[1970:506713] 第2次_<NSThread: 0x6000000713c0>{number = 5, name = (null)}ThreadDemo[1970:506691] 第3次_<NSThread: 0x600000070f40>{number = 6, name = (null)}ThreadDemo[1970:506694] 第4次_<NSThread: 0x600000070440>{number = 7, name = (null)}
-(void)GCDSemaphore{ // dispatch_semaphore_t semaphore = dispatch_semaphore_create(1); dispatch_apply(5, self.concurrentQueue, ^(size_t i) { dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER); dispatch_async(self.concurrentQueue, ^{ NSLog(@"第%@次_%@",@(i),[NSThread currentThread]); dispatch_semaphore_signal(semaphore); }); });}
ThreadDemo[2020:513651] 第0次_<NSThread: 0x608000073900>{number = 3, name = (null)}ThreadDemo[2020:513651] 第1次_<NSThread: 0x608000073900>{number = 3, name = (null)}ThreadDemo[2020:513651] 第2次_<NSThread: 0x608000073900>{number = 3, name = (null)}ThreadDemo[2020:513651] 第3次_<NSThread: 0x608000073900>{number = 3, name = (null)}ThreadDemo[2020:513651] 第4次_<NSThread: 0x608000073900>{number = 3, name = (null)}
信号量是管制工作执行的重要条件,当信号量为0时,所有工作期待,信号量越大,容许可并行执行的工作数量越多。
1.4 NSOperation
NSOperation
以及NSOperationQueue
是苹果对于GCD的封装,其中NSOperation
其实就是咱们下面所说的工作,然而这个类不能间接应用,咱们要用他的两个子类,NSBlockOperation
和NSInvocationOperation
,而NSOperationQueue
呢,其实就是相似于GCD中的队列,用于治理你退出到其中的工作。
NSOperation提供了对于工作的执行,勾销,以及随时获取工作的状态,增加工作依赖以及优先级等办法和属性,绝对于GCD提供的办法来说,更直观,更不便,并且提供了更多的管制接口。(很多时候,苹果设计的架构是很棒的,不要只是在乎他实现了什么,可能你学到的货色会更多),有几个办法和属性咱们理解一下
@interface NSOperation : NSObject {@private id _private; int32_t _private1;#if __LP64__ int32_t _private1b;#endif}- (void)start;//启动工作 默认在以后线程执行- (void)main;//自定义NSOperation,写一个子类,重写这个办法,在这个办法外面增加须要执行的操作。@property (readonly, getter=isCancelled) BOOL cancelled;//是否曾经勾销,只读- (void)cancel;//勾销工作@property (readonly, getter=isExecuting) BOOL executing;//正在执行,只读@property (readonly, getter=isFinished) BOOL finished;//执行完结,只读@property (readonly, getter=isConcurrent) BOOL concurrent; // To be deprecated; use and override 'asynchronous' below@property (readonly, getter=isAsynchronous) BOOL asynchronous NS_AVAILABLE(10_8, 7_0);//是否并发,只读@property (readonly, getter=isReady) BOOL ready;//筹备执行- (void)addDependency:(NSOperation *)op;//增加依赖- (void)removeDependency:(NSOperation *)op;//移除依赖@property (readonly, copy) NSArray<NSOperation *> *dependencies;//所有依赖关系,只读typedef NS_ENUM(NSInteger, NSOperationQueuePriority) { NSOperationQueuePriorityVeryLow = -8L, NSOperationQueuePriorityLow = -4L, NSOperationQueuePriorityNormal = 0, NSOperationQueuePriorityHigh = 4, NSOperationQueuePriorityVeryHigh = 8};//零碎提供的优先级关系枚举@property NSOperationQueuePriority queuePriority;//执行优先级@property (nullable, copy) void (^completionBlock)(void) NS_AVAILABLE(10_6, 4_0);//工作执行实现之后的回调- (void)waitUntilFinished NS_AVAILABLE(10_6, 4_0);//阻塞以后线程,等到某个operation执行结束。@property double threadPriority NS_DEPRECATED(10_6, 10_10, 4_0, 8_0);//已废除,用qualityOfService代替。@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的实现操作。@property (nullable, copy) NSString *name NS_AVAILABLE(10_10, 8_0);//工作名称@end
然而NSOperation
自身是个抽象类,不能间接应用,咱们有三种形式赋予它新的生命,就是上面这三个货色。
NSOperation自定义子类
这是我要说的第一个工作类型,咱们能够自定义继承于NSOperation
的子类,并重写父类提供的办法,实现一波具备非凡意义的工作。比方咱们去下载一个图片:
.h#import <UIKit/UIKit.h>@protocol QPImageDownLoadOperationDelegate <NSObject>-(void)QPImageDownLoadFinished:(UIImage*)image;@end@interface QPImageDownLoadOperation : NSOperation-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<QPImageDownLoadOperationDelegate>)delegate;@end.m#import "QPImageDownLoadOperation.h"@implementation QPImageDownLoadOperation{ NSURL *_imageUrl; id _delegate;}-(id)initOperationWithUrl:(NSURL*)imageUrl delegate:(id<QPImageDownLoadOperationDelegate>)delegate{ if (self == [super init]) { _imageUrl = imageUrl; _delegate = delegate; } return self;}-(void)main{ @autoreleasepool { UIImage *image = [self imageWithUrl:_imageUrl]; if (_delegate && [_delegate respondsToSelector:@selector(QPImageDownLoadFinished:)]) { [_delegate QPImageDownLoadFinished:image]; } }}-(UIImage*)imageWithUrl:(NSURL*)url{ NSData *imageData = [NSData dataWithContentsOfURL:url]; UIImage *image = [UIImage imageWithData:imageData]; return image;}@end而后调用:-(void)QPDownLoadImageOperationRun{ QPImageDownLoadOperation *ysOper = [[QPImageDownLoadOperation alloc] initOperationWithUrl:[NSURL URLWithString:@"http://img5.duitang.com/uploads/item/201206/06/20120606174422_LZSeE.thumb.700_0.jpeg"] delegate:self]; [ysOper start];}-(void)QPImageDownLoadFinished:(UIImage *)image{ NSLog(@"%@",image);}
运行打印后果:
ThreadDemo[4141:1100329] <UIImage: 0x60800009f630>, {700, 1050}
NSBlockOperation
第二个,就是零碎提供的NSOperation
的子类NSBlockOperation
,咱们看一下他提供的API:
@interface NSBlockOperation : NSOperation {@private id _private2; void *_reserved2;}+ (instancetype)blockOperationWithBlock:(void (^)(void))block;- (void)addExecutionBlock:(void (^)(void))block;@property (readonly, copy) NSArray<void (^)(void)> *executionBlocks;@end
很简略,就这几个,咱们就用它实现一个工作:
-(void)NSBlockOperationRun{ NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"NSBlockOperationRun_%@_%@",[NSOperationQueue currentQueue],[NSThread currentThread]); }]; [blockOper start];}
运行后果:
ThreadDemo[4313:1121900] NSBlockOperationRun_<NSOperationQueue: 0x608000037420>{name = 'NSOperationQueue Main Queue'}_<NSThread: 0x60000006dd80>{number = 1, name = main}
咱们发现这个工作是在以后线程程序执行的,咱们发现还有一个办法addExecutionBlock:
试一下:
-(void)NSBlockOperationRun{ NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"NSBlockOperationRun_1_%@",[NSThread currentThread]); }]; [blockOper addExecutionBlock:^{ NSLog(@"NSBlockOperationRun_2_%@",[NSThread currentThread]); }]; [blockOper addExecutionBlock:^{ NSLog(@"NSBlockOperationRun_3_%@",[NSThread currentThread]); }]; [blockOper addExecutionBlock:^{ NSLog(@"NSBlockOperationRun_4_%@",[NSThread currentThread]); }]; [blockOper start];}
打印后果:
ThreadDemo[4516:1169835] NSBlockOperationRun_1_<NSThread: 0x60000006d880>{number = 1, name = main}ThreadDemo[4516:1169875] NSBlockOperationRun_3_<NSThread: 0x600000070800>{number = 4, name = (null)}ThreadDemo[4516:1169877] NSBlockOperationRun_4_<NSThread: 0x6080000762c0>{number = 5, name = (null)}ThreadDemo[4516:1169893] NSBlockOperationRun_2_<NSThread: 0x608000076100>{number = 3, name = (null)}
从打印后果来看,这个4个工作是异步并发执行的,开拓了多条线程。
NSInvocationOperation
第三个,就是它了,同样也是零碎提供给咱们的一个工作类,基于一个target对象以及一个selector来创立工作,具体代码:
-(void)NSInvocationOperationRun{ NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil]; [invocationOper start];}-(void)invocationOperSel{ NSLog(@"NSInvocationOperationRun_%@",[NSThread currentThread]);}
运行后果:
ThreadDemo[4538:1173118] NSInvocationOperationRun_<NSThread: 0x60800006e900>{number = 1, name = main}
运行后果与NSBlockOperation
单个block函数的执行形式雷同,同步程序执行。确实零碎的封装给予咱们对于工作更直观的货色,然而对于多个工作的管制机制并不欠缺。
NSOperationQueue
下面说道咱们创立的NSOperation
工作对象能够通过start
办法来执行,同样咱们能够把这个工作对象增加到一个NSOperationQueue对象中去执行,先看一下零碎的API:
@interface NSOperationQueue : NSObject {@private id _private; void *_reserved;}- (void)addOperation:(NSOperation *)op;//增加工作- (void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait NS_AVAILABLE(10_6, 4_0);//增加一组工作- (void)addOperationWithBlock:(void (^)(void))block NS_AVAILABLE(10_6, 4_0);//增加一个block模式的工作@property (readonly, copy) NSArray<__kindof NSOperation *> *operations;//队列中所有的工作数组@property (readonly) NSUInteger operationCount NS_AVAILABLE(10_6, 4_0);//队列中的工作数@property NSInteger maxConcurrentOperationCount;//最大并发数@property (getter=isSuspended) BOOL suspended;//暂停@property (nullable, copy) NSString *name NS_AVAILABLE(10_6, 4_0);//名称@property NSQualityOfService qualityOfService NS_AVAILABLE(10_10, 8_0);//服务质量,一个高质量的服务就意味着更多的资源得以提供来更快的实现操作。@property (nullable, assign /* actually retain */) dispatch_queue_t underlyingQueue NS_AVAILABLE(10_10, 8_0);- (void)cancelAllOperations;//勾销队列中的所有工作- (void)waitUntilAllOperationsAreFinished;//阻塞以后线程,等到队列中的工作全副执行结束。#if FOUNDATION_SWIFT_SDK_EPOCH_AT_LEAST(8)@property (class, readonly, strong, nullable) NSOperationQueue *currentQueue NS_AVAILABLE(10_6, 4_0);//获取以后队列@property (class, readonly, strong) NSOperationQueue *mainQueue NS_AVAILABLE(10_6, 4_0);//获取主队列#endif@end
-(void)NSOperationQueueRun{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSInvocationOperation *invocationOper = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperSel) object:nil]; [queue addOperation:invocationOper]; NSBlockOperation *blockOper = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"NSBlockOperationRun_%@",[NSThread currentThread]); }]; [queue addOperation:blockOper]; [queue addOperationWithBlock:^{ NSLog(@"QUEUEBlockOperationRun_%@",[NSThread currentThread]); }];}
打印后果:
ThreadDemo[4761:1205689] NSBlockOperationRun_<NSThread: 0x600000264480>{number = 4, name = (null)}ThreadDemo[4761:1205691] NSInvocationOperationRun_<NSThread: 0x600000264380>{number = 3, name = (null)}ThreadDemo[4761:1205706] QUEUEBlockOperationRun_<NSThread: 0x6000002645c0>{number = 5, name = (null)}
咱们发现,退出队列之后不必调用工作的start
办法,队列会帮你治理工作的执行状况。上述执行后果阐明这些工作在队列中为并发执行的。
增加依赖关系
-(void)NSOperationQueueRun{ NSOperationQueue *queue = [[NSOperationQueue alloc] init]; NSBlockOperation *blockOper_1 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 1000; i++) { NSLog(@"blockOper_1_%@_%@",@(i),[NSThread currentThread]); } }]; NSBlockOperation *blockOper_2 = [NSBlockOperation blockOperationWithBlock:^{ for (int i = 0; i < 1000; i++) { NSLog(@"blockOper_2_%@_%@",@(i),[NSThread currentThread]); } }]; [blockOper_1 addDependency:blockOper_2]; [queue addOperation:blockOper_1]; [queue addOperation:blockOper_2];}
打印后果:
ThreadDemo[5066:1233824] blockOper_2_0_<NSThread: 0x600000078340>{number = 3, name = (null)}ThreadDemo[5066:1233824] blockOper_2_1_<NSThread: 0x600000078340>{number = 3, name = (null)}ThreadDemo[5066:1233824] blockOper_2_2_<NSThread: 0x600000078340>{number = 3, name = (null)}ThreadDemo[5066:1233824] blockOper_2_3_<NSThread: 0x600000078340>{number = 3, name = (null)}... ...ThreadDemo[5066:1233824] blockOper_2_999_<NSThread: 0x600000078340>{number = 3, name = (null)}ThreadDemo[5066:1233822] blockOper_1_0_<NSThread: 0x60000006ae80>{number = 4, name = (null)}... ...ThreadDemo[5066:1233822] blockOper_1_997_<NSThread: 0x60000006ae80>{number = 4, name = (null)}ThreadDemo[5066:1233822] blockOper_1_998_<NSThread: 0x60000006ae80>{number = 4, name = (null)}ThreadDemo[5066:1233822] blockOper_1_999_<NSThread: 0x60000006ae80>{number = 4, name = (null)}
通过打印后果咱们能够看到,增加依赖之后,依赖工作必须期待被依赖工作执行结束之后才会开始执行。⚠️,就算依赖工作的优先级再高,也是被依赖工作先执行,同时,和优先级不同,依赖关系不受队列的局限,只有是我依赖于你,那你必须先执行完,我才执行。
队列的最大并发数
就是说,这个队列最多能够有多少工作同时执行,或者说最多开拓多少条线程,如果设置为1,那就一次只能执行一个工作,然而,不要认为这和GCD的串行队列一样,就算最大并发数为1,队列工作的执行程序仍然取决于很多因素。
2. 线程锁
锁 是什么意思?
咱们在应用多线程的时候多个线程可能会拜访同一块资源,这样就很容易引发数据错乱和数据安全等问题,这时候就须要咱们保障每次只有一个线程拜访这一块资源,锁 应运而生。
2.1 OSSpinLock 自旋锁
#import <libkern/OSAtomic.h>
__block OSSpinLock oslock = OS_SPINLOCK_INIT;//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 筹备上锁"); OSSpinLockLock(&oslock); sleep(4); NSLog(@"线程1"); OSSpinLockUnlock(&oslock); NSLog(@"线程1 解锁胜利"); NSLog(@"--------------------------------------------------------");});//线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 筹备上锁"); OSSpinLockLock(&oslock); NSLog(@"线程2"); OSSpinLockUnlock(&oslock); NSLog(@"线程2 解锁胜利");});
咱们来批改一下代码:
__block OSSpinLock oslock = OS_SPINLOCK_INIT;//线程1 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{......//OSSpinLockUnlock(&oslock);......
在 OSSpinLock1
图中能够发现:当咱们锁住线程1时,在同时锁住线程2的状况下,线程2会始终期待(自旋锁不会让期待的进入睡眠状态),直到线程1的工作执行完且解锁结束,线程2会立刻执行;而在 OSSpinLock2
图中,因为咱们正文掉了线程1中的解锁代码,会绕过线程1,直到调用了线程2的解锁办法才会继续执行线程1中的工作,失常状况下,lock
和unlock
最好成对呈现。
OS_SPINLOCK_INIT: 默认值为 0
,在 locked
状态时就会大于 0
,unlocked
状态下为 0
OSSpinLockLock(&oslock):上锁,参数为 OSSpinLock
地址
OSSpinLockUnlock(&oslock):解锁,参数为 OSSpinLock
地址
OSSpinLockTry(&oslock):尝试加锁,能够加锁则立刻加锁并返回 YES
,反之返回 NO
这里顺便提一下trylock
和lock
应用场景:
以后线程锁失败,也能够持续其它工作,用 trylock 适合
以后线程只有锁胜利后,才会做一些有意义的工作,那就 lock,没必要轮询 trylock
2.2 dispatch_semaphore 信号量
dispatch_semaphore_t signal = dispatch_semaphore_create(1); //传入值必须 >=0, 若传入为0则阻塞线程并期待timeout,工夫到后会执行其后的语句dispatch_time_t overTime = dispatch_time(DISPATCH_TIME_NOW, 3.0f * NSEC_PER_SEC);//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 期待ing"); dispatch_semaphore_wait(signal, overTime); //signal 值 -1 NSLog(@"线程1"); dispatch_semaphore_signal(signal); //signal 值 +1 NSLog(@"线程1 发送信号"); NSLog(@"--------------------------------------------------------");});//线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 期待ing"); dispatch_semaphore_wait(signal, overTime); NSLog(@"线程2"); dispatch_semaphore_signal(signal); NSLog(@"线程2 发送信号");});
dispatch_semaphore_create(1): 传入值必须 >=0
, 若传入为 0
则阻塞线程并期待timeout,工夫到后会执行其后的语句
dispatch_semaphore_wait(signal, overTime):能够了解为 lock
,会使得 signal
值 -1
dispatch_semaphore_signal(signal):能够了解为 unlock
,会使得 signal
值 +1
对于信号量,咱们能够用停车来比喻:
停车场残余4个车位,那么即便同时来了四辆车也能停的下。如果此时来了五辆车,那么就有一辆须要期待。
信号量的值(signal)就相当于残余车位的数目,dispatch_semaphore_wait
函数就相当于来了一辆车,dispatch_semaphore_signal
就相当于走了一辆车。停车位的残余数目在初始化的时候就曾经指明了(dispatch_semaphore_create(long value)),调用一次 dispatch_semaphore_signal,残余的车位就减少一个;调用一次dispatch_semaphore_wait 残余车位就缩小一个;当残余车位为 0 时,再来车(即调用 dispatch_semaphore_wait)就只能期待。有可能同时有几辆车期待一个停车位。有些车主没有急躁,给本人设定了一段等待时间,这段时间内等不到停车位就走了,如果等到了就开进去停车。而有些车主就像把车停在这,所以就始终等上来。
运行后果:
能够发现,因为咱们初始化信号量的时候是大于 0
的,所以并没有阻塞线程,而是间接执行了 线程1 线程2。
咱们把 信号量初始值改为 0
:
能够看到这时候咱们设置的 overTime
失效了。
2.3 pthread_mutex 互斥锁
ibireme 在《不再平安的 OSSpinLock》这篇文章中提到性能最好的 OSSpinLock
曾经不再是线程平安的并把本人开源我的项目中的 OSSpinLock
都替换成了 pthread_mutex
。
特意去看了下源码,总结了下常见用法:
#import <pthread.h>
static pthread_mutex_t pLock;pthread_mutex_init(&pLock, NULL); //1.线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 筹备上锁"); pthread_mutex_lock(&pLock); sleep(3); NSLog(@"线程1"); pthread_mutex_unlock(&pLock);});//1.线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 筹备上锁"); pthread_mutex_lock(&pLock); NSLog(@"线程2"); pthread_mutex_unlock(&pLock);});
pthread_mutex 中也有个pthread_mutex_trylock(&pLock)
,和下面提到的 OSSpinLockTry(&oslock)
区别在于,前者能够加锁时返回的是 0
,否则返回一个谬误提醒码;后者返回的 YES
和NO
pthread_mutex(recursive) 递归锁
通过下面几种例子,咱们能够发现:加锁后只能有一个线程拜访该对象,前面的线程须要排队,并且 lock 和 unlock 是对应呈现的,同一线程屡次 lock 是不容许的,而递归锁容许同一个线程在未开释其领有的锁时重复对该锁进行加锁操作。
例子:
static pthread_mutex_t pLock;pthread_mutexattr_t attr;pthread_mutexattr_init(&attr); //初始化attr并且给它赋予默认pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); //设置锁类型,这边是设置为递归锁pthread_mutex_init(&pLock, &attr);pthread_mutexattr_destroy(&attr); //销毁一个属性对象,在从新进行初始化之前该构造不能从新应用//1.线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { pthread_mutex_lock(&pLock); if (value > 0) { NSLog(@"value: %d", value); RecursiveBlock(value - 1); } pthread_mutex_unlock(&pLock); }; RecursiveBlock(5);});
下面的代码如果咱们用 pthread_mutex_init(&pLock, NULL)
初始化会呈现死锁的状况,递归锁能很好的防止这种状况的死锁;
2.4 NSLock 互斥锁
NSLock是对mutex一般锁的封装,是Foundation框架中以对象模式裸露给开发者的一种锁,(Foundation框架同时提供了NSConditionLock
,NSRecursiveLock
,NSCondition
)
NSLock API 很少也很简略:
lock、unlock:不多做解释,和下面一样
trylock:能加锁返回 YES 并执行加锁操作,相当于 lock,反之返回 NO
** lockBeforeDate:这个办法示意会在传入的工夫内尝试加锁,若能加锁则执行加锁**操作并返回 YES,反之返回 NO
NSLock *lock = [NSLock new];//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程1 尝试减速ing..."); [lock lock]; sleep(3);//睡眠5秒 NSLog(@"线程1"); [lock unlock]; NSLog(@"线程1解锁胜利");});//线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"线程2 尝试减速ing..."); BOOL x = [lock lockBeforeDate:[NSDate dateWithTimeIntervalSinceNow:4]]; if (x) { NSLog(@"线程2"); [lock unlock]; }else{ NSLog(@"失败"); }});
#### 2.5 NSCondition 条件锁
NSCondition是对mutex和cond的封装
咱们先来看看 API:
wait:进入期待状态
waitUntilDate::让一个线程期待肯定的工夫
signal:唤醒一个期待的线程
broadcast:唤醒所有期待的线程
NSCondition *cLock = [NSCondition new];//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"start"); [cLock lock]; [cLock waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:2]]; NSLog(@"线程1"); [cLock unlock];});
- 唤醒一个期待线程
NSCondition *cLock = [NSCondition new];//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cLock lock]; NSLog(@"线程1加锁胜利"); [cLock wait]; NSLog(@"线程1"); [cLock unlock];});//线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cLock lock]; NSLog(@"线程2加锁胜利"); [cLock wait]; NSLog(@"线程2"); [cLock unlock];});dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ sleep(2); NSLog(@"唤醒一个期待的线程"); [cLock signal];});
2.6 NSRecursiveLock 递归锁
NSRecursiveLock也是对mutex递归锁的封装,API跟NSLock基本一致。
递归锁能够被同一线程屡次申请,而不会引起死锁。这次要是用在循环或递归操作中。
NSLock *rLock = [NSLock new];dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ static void (^RecursiveBlock)(int); RecursiveBlock = ^(int value) { [rLock lock]; if (value > 0) { NSLog(@"线程%d", value); RecursiveBlock(value - 1); } [rLock unlock]; }; RecursiveBlock(4);});
这段代码是一个典型的死锁状况。在咱们的线程中,RecursiveMethod
是递归调用的。所以每次进入这个 block 时,都会去加一次锁,而从第二次开始,因为锁曾经被应用了且没有解锁,所以它须要期待锁被解除,这样就导致了死锁,线程被阻塞住了。
将 NSLock 替换为 NSRecursiveLock:
NSRecursiveLock 办法里还提供了两个办法,用法和下面介绍的根本没什么差异,这里不过多介绍了:
- (BOOL)tryLock;- (BOOL)lockBeforeDate:(NSDate *)limit;
2.7 @synchronized 互斥锁
@synchronized是对mutex递归锁的封装
@synchronized(obj)外部会生成obj对应的递归锁,而后进行加锁、解锁操作
//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized (self) { sleep(2); NSLog(@"线程1"); }});//线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ @synchronized (self) { NSLog(@"线程2"); }});
2.8 NSConditionLock 条件锁
NSConditionLock是对NSCondition的进一步封装,能够设置具体的条件值
相比于 NSLock 多了个 condition
参数,咱们能够了解为一个条件标示。
例子:
NSConditionLock *cLock = [[NSConditionLock alloc] initWithCondition:0];//线程1dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if([cLock tryLockWhenCondition:0]){ NSLog(@"线程1"); [cLock unlockWithCondition:1]; }else{ NSLog(@"失败"); }});//线程2dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cLock lockWhenCondition:3]; NSLog(@"线程2"); [cLock unlockWithCondition:2];});//线程3dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [cLock lockWhenCondition:1]; NSLog(@"线程3"); [cLock unlockWithCondition:3];});
运行后果:
- 咱们在初始化 NSConditionLock 对象时,给了他的标示为
0
- 执行
tryLockWhenCondition:
时,咱们传入的条件标示也是0
,所 以线程1 加锁胜利 - 执行
unlockWithCondition:
时,这时候会把condition
由0
批改为1
- 因为
condition
批改为了1
, 会先走到 线程3,而后 线程3 又将condition
批改为3
- 最初 走了 线程2 的流程
从下面的后果咱们能够发现,NSConditionLock 还能够实现工作之间的依赖。
线程锁总结
锁的概念定义:(参考维基百科)
- 临界区:指的是一块对公共资源进行拜访的代码,并非一种机制或是算法。
- 自旋锁:是用于多线程同步的一种锁,线程重复查看锁变量是否可用。因为线程在这一过程中放弃执行,因而是一种
忙期待
。一旦获取了自旋锁,线程会始终放弃该锁,直至显式开释自旋锁。 自旋锁防止了过程上下文的调度开销,因而对于线程只会阻塞很短时间的场合是无效的。 - 互斥锁(Mutex):是一种用于多线程编程中,避免两条线程同时对同一公共资源(比方全局变量)进行读写的机制。该目标通过将代码切片成一个一个的临界区而达成。
- 读写锁:是计算机程序的并发管制的一种同步机制,也称“共享-互斥锁”、多读者-单写者锁) 用于解决多线程对公共资源读写问题。读操作可并发重入,写操作是互斥的。 读写锁通常用互斥锁、条件变量、信号量实现。
- 信号量(semaphore):是一种更高级的同步机制,
互斥锁
能够说是semaphore在仅取值0/1时的特例。信号量
能够有更多的取值空间,用来实现更加简单的同步,而不单单是线程间互斥。 - 条件锁:就是条件变量,当过程的某些资源要求不满足时就进入休眠,也就是锁住了。当资源被调配到了,条件锁关上,过程持续运行。
3.runloop机制
- 首先说介绍下NSRunLoopOSX/iOS 零碎中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef。CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程平安的。NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,然而这些 API 不是线程平安的。
- NSRunLoop 与线程的关系
- 其实runLoop就是一个do ... while()函数,每个runLoop对应一个线程他们是一一对应的关系,关系保留在一个全局的Dictionary里边,线程刚创立时没有RunLoop,如果不被动获取,是不会有的,RunLoop的创立产生在第一次获取时,RunLoop的销毁产生在线程完结,只能在一个线程的外部获取它的RunLoop(主线程除外)主线程默认有个RunLoop.
- Thread蕴含一个CFRunLoop,一个CFRunLoop蕴含一种CFRunLoopMode,mode蕴含CFRunLoopSource,CFRunLoopTimer和CFRunLoopObserver。
- RunLoop只能运行在一种mode下,如果要换mode以后的loop也须要停下重启成新的。利用这个机制,ScrollView过程中NSDefaultRunLoopMode的mode会切换UITrackingRunLoopMode来保障ScrollView的晦涩滑动不受只能在NSDefaultRunLoopMode时解决的事件影响滑动。同时mode还是可定制的。
NSDefaultRunLoopMode:默认,闲暇状态
UITrackingRunLoopMode:ScrollView滑动时UIInitializationRunLoopMode:启动时NSRunLoopCommonModes:Mode汇合 Timer计时会被scrollView的滑动影响的问题能够通过将timer增加到NSRunLoopCommonModes来解决
//将timer增加到NSDefaultRunLoopMode中[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];//而后再增加到NSRunLoopCommonModes里NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTick:) userInfo:nil repeats:YES];[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRefstatic CFMutableDictionaryRef loopsDic;/// 拜访 loopsDic 时的锁static CFSpinLock_t loopsLock;/// 获取一个 pthread 对应的 RunLoop。CFRunLoopRef _CFRunLoopGet(pthread_t thread) { OSSpinLockLock(&loopsLock); if (!loopsDic) { // 第一次进入时,初始化全局Dic,并先为主线程创立一个 RunLoop。 loopsDic = CFDictionaryCreateMutable(); CFRunLoopRef mainLoop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop); } /// 间接从 Dictionary 里获取。 CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread)); if (!loop) { /// 取不到时,创立一个 loop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic, thread, loop); /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。 _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop); } OSSpinLockUnLock(&loopsLock); return loop;}CFRunLoopRef CFRunLoopGetMain() { return _CFRunLoopGet(pthread_main_thread_np());}CFRunLoopRef CFRunLoopGetCurrent() { return _CFRunLoopGet(pthread_self());}
介绍完线程和RunLoop的关系后,次要看下RunLoop和RunLoopModel, RunLoop runLoop 在CoreFoundation对外一共有五大类最为对外的接口:
RunLoop
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
1. 其中 CFRunLoopModeRef 类并没有对外裸露,只是通过 CFRunLoopRef 的接口进行了封装
一个 RunLoop 蕴含若干个 Mode,每个 Mode 又蕴含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果须要切换 Mode,只能退出 Loop,再从新指定一个 Mode 进入。这样做次要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
2. CFRunLoopSourceRef 是事件产生的中央,Source蕴含了两个局部:Source0 和 Source1。source0:解决如UIEvent,CFSocket这样的事件source1:Mach port驱动,CFMachport,CFMessagePortSource0 只蕴含了一个回调(函数指针),它并不能被动触发事件。应用时,你须要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,而后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其解决这个事件。Source1 蕴含了一个 mach_port 和一个回调(函数指针),被用于和通过内核和其余 mach_port 互相发送音讯。这种 Source 能被动唤醒 RunLoop 的线程,其原理在上面会讲到。
3. CFRunLoopTimerRefNSTimer是对RunLoopTimer的封装是基于工夫的触发器,它和 NSTimer 是toll-free bridge 的。其蕴含一个工夫长度和一个回调(函数指针)。当其退出到 RunLoop 时,RunLoop会注册对应的工夫点,当工夫点到时,RunLoop会被唤醒以执行那个回调
4. CFRunLoopObserverRefCocoa框架中很多机制比方CAAnimation等都是由RunLoopObserver触发的。observer到以后状态的变动进行告诉观察者,每个 Ovserver 都蕴含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调承受到这个变动。能够观测的工夫点有以下几个:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 行将进入Loop kCFRunLoopBeforeTimers = (1UL << 1), // 行将解决 Timer kCFRunLoopBeforeSources = (1UL << 2), // 行将解决 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 行将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7), // 行将退出Loop};
RunLoopModelCFRunLoopMode 和 CFRunLoop 的构造大抵如下:
struct __CFRunLoopMode { CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode" CFMutableSetRef _sources0; // Set CFMutableSetRef _sources1; // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers; // Array ...};struct __CFRunLoop { CFMutableSetRef _commonModes; // Set CFMutableSetRef _commonModeItems; // Set CFRunLoopModeRef _currentMode; // Current Runloop Mode CFMutableSetRef _modes; // Set ...};
这里有个概念叫 "CommonModes":一个 Mode 能够将本人标记为"Common"属性(通过将其 ModeName 增加到 RunLoop 的 "commonModes" 中)。RunLoop 会主动将 _commonModeItems 退出到具备 "Common" 标记的所有Mode里。利用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode 和 UITrackingRunLoopMode。这两个 Mode 都曾经被标记为"Common"属性。DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态。当你创立一个 Timer 并加到 DefaultMode 时,Timer 会失去反复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 TrackingRunLoopMode,这时 Timer 就不会被回调,并且也不会影响到滑动操作。有时你须要一个 Timer,在两个 Mode 中都能失去回调,一种方法就是将这个 Timer 别离退出这两个 Mode。还有一种形式,就是将 Timer 退出到顶层的 RunLoop 的 "commonModeItems" 中。"commonModeItems" 被 RunLoop 自动更新到所有具备"Common"属性的 Mode 里去。
RunLoop底层实现从下面代码能够看到,RunLoop的外围是基于 mach port 的,其进入休眠时调用的函数是 mach_msg()。为了解释这几个概念,上面略微介绍一下OSX/iOS的零碎架构。
苹果官网将整个零碎大抵划分为上述4个档次:应用层包含用户能接触到的图形利用,例如 Spotlight、Aqua、SpringBoard等。利用框架层即开发人员接触到的 Cocoa 等框架。外围框架层包含各种外围框架、OpenGL 等内容。Darwin 即操作系统的外围,包含零碎内核、驱动、Shell 等内容,这一层是开源的,其所有源码都能够在 opensource.apple.com 里找到。
CFRunLoop { current mode = kCFRunLoopDefaultMode common modes = { UITrackingRunLoopMode kCFRunLoopDefaultMode } common mode items = { // source0 (manual) CFRunLoopSource {order =-1, { callout = _UIApplicationHandleEventQueue}} CFRunLoopSource {order =-1, { callout = PurpleEventSignalCallback }} CFRunLoopSource {order = 0, { callout = FBSSerialQueueRunLoopSourceHandler}} // source1 (mach port) CFRunLoopSource {order = 0, {port = 17923}} CFRunLoopSource {order = 0, {port = 12039}} CFRunLoopSource {order = 0, {port = 16647}} CFRunLoopSource {order =-1, { callout = PurpleEventCallback}} CFRunLoopSource {order = 0, {port = 2407, callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}} CFRunLoopSource {order = 0, {port = 1c03, callout = __IOHIDEventSystemClientAvailabilityCallback}} CFRunLoopSource {order = 0, {port = 1b03, callout = __IOHIDEventSystemClientQueueCallback}} CFRunLoopSource {order = 1, {port = 1903, callout = __IOMIGMachPortPortCallback}} // Ovserver CFRunLoopObserver {order = -2147483647, activities = 0x1,// Entry callout = _wrapRunLoopWithAutoreleasePoolHandler} CFRunLoopObserver {order = 0, activities = 0x20, // BeforeWaiting callout = _UIGestureRecognizerUpdateObserver} CFRunLoopObserver {order = 1999000, activities = 0xa0, // BeforeWaiting | Exit callout = _afterCACommitHandler} CFRunLoopObserver {order = 2000000, activities = 0xa0, // BeforeWaiting | Exit callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv} CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit callout = _wrapRunLoopWithAutoreleasePoolHandler} // Timer CFRunLoopTimer {firing = No, interval = 3.1536e+09, tolerance = 0, next fire date = 453098071 (-4421.76019 @ 96223387169499), callout = _ZN2CAL14timer_callbackEP16__CFRunLoopTimerPv (QuartzCore.framework)} }, modes = { CFRunLoopMode { sources0 = { /* same as 'common mode items' */ }, sources1 = { /* same as 'common mode items' */ }, observers = { /* same as 'common mode items' */ }, timers = { /* same as 'common mode items' */ }, }, CFRunLoopMode { sources0 = { /* same as 'common mode items' */ }, sources1 = { /* same as 'common mode items' */ }, observers = { /* same as 'common mode items' */ }, timers = { /* same as 'common mode items' */ }, }, CFRunLoopMode { sources0 = { CFRunLoopSource {order = 0, { callout = FBSSerialQueueRunLoopSourceHandler}} }, sources1 = (null), observers = { CFRunLoopObserver >{activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv} )}, timers = (null), }, CFRunLoopMode { //这是一个占位的 Mode,没有理论作用。 sources0 = { CFRunLoopSource {order = -1, { callout = PurpleEventSignalCallback}} }, sources1 = { CFRunLoopSource {order = -1, { callout = PurpleEventCallback}} }, observers = (null), timers = (null), }, CFRunLoopMode { sources0 = (null), sources1 = (null), observers = (null), timers = (null), } }}
这里能看到苹果所有的Model
事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接管零碎事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。当一个硬件事件(触摸/锁屏/摇摆等)产生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接管。这个过程的详细情况能够参考 这里 。SpringBoard 只接管按键(锁屏/静音等),触摸,减速,靠近传感器等几种 Event,随后用 mach port 转发给须要的App过程。随后苹果注册的那个 Source1 就会触发回调,并调用 _UIApplicationHandleEventQueue() 进行利用外部的散发。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 解决并包装成 UIEvent 进行解决或散发,其中包含辨认 UIGesture/解决屏幕旋转/发送给 UIWindow 等。通常事件比方 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中实现的。####手势辨认当下面的 _UIApplicationHandleEventQueue() 辨认了一个手势时,其首先会调用 Cancel 将以后的 touchesBegin/Move/End 系列回调打断。随后零碎将对应的 UIGestureRecognizer 标记为待处理。苹果注册了一个 Observer 监测 BeforeWaiting (Loop行将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver(),其外部会获取所有刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。当有 UIGestureRecognizer 的变动(创立/销毁/状态扭转)时,这个回调都会进行相应解决。
界面更新
当在操作 UI 时,比方扭转了 Frame、更新了 UIView/CALayer 的档次时,或者手动调用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay办法后,这个 UIView/CALayer 就被标记为待处理,并被提交到一个全局的容器去。苹果注册了一个 Observer 监听 BeforeWaiting(行将进入休眠) 和 Exit (行将退出Loop) 事件,回调去执行一个很长的函数:_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。这个函数里会遍历所有待处理的 UIView/CAlayer 以执行理论的绘制和调整,并更新 UI 界面。
NSRunLoop的利用
AFNetworking 创立一个常驻服务线程去解决数据返回应用NSOperation+NSURLConnection并发模型都会面临NSURLConnection下载实现火线程退出导致NSOperation对象接管不到回调的问题。AFNetWorking解决这个问题的办法是依照官网的guid上写的NSURLConnection的delegate办法须要在connection发动的线程runloop中调用,于是AFNetWorking间接借鉴了Apple本人的一个Demo的实现办法独自起一个global thread,内置一个runloop,所有的connection都由这个runloop发动,回调也是它接管,不占用主线程,也不耗CPU资源。##TableView中实现平滑滚动提早加载图片利用CFRunLoopMode的个性,能够将图片的加载放到NSDefaultRunLoopMode的mode里,这样在滚动UITrackingRunLoopMode这个mode时不会被加载而影响到。
UIImage *downloadedImage = ...;[self.avatarImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
应用RunLoop阻塞线程并且同时可能解决其余source事件
CFRunLoopRunInMode(kCFRunLoopDefaultMode,second, NO);
CFRunLoopStop(CFRunLoopGetMain());