014-多线程
一、Pthreads
Pthreads:POSIX threads,线程的POSIX标准。该标准定义了创建和操纵线程的一套API。它是基于底层的,一般我们在iOS开发中也不会用到的
二、NSThread
1.概述
这套方案是经过苹果封装后的,并且完全是面向对象的。所以你可以直接操控线程对象,非常直观和方便。但是,它的生命周期还是需要我们程序员去手动管理,所以这套方案也是偶尔用用
2.使用方法
1)创建线程
// 第一种创建方法:手动创建,手动启动 // 1) SEL对象形式 // 创建线程 NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(run1:) object:nil]; // 启动线程 [thread1 start]; // 2) Block形式 // 创建线程 NSThread *thread2 = [[NSThread alloc] initWithBlock:^{ // 代码... }]; // 启动线程 [thread1 start]; // 第二种创建方法:手动创建,自动启动 // 创建线程之后,自动默认启动线程(注意:这种创建方法是拿不到这个线程对象) // 1) SEL对象形式 [NSThread detachNewThreadSelector:@selector(run2:) toTarget:self withObject:nil]; // 2) Block形式 [NSThread detachNewThreadWithBlock:^{ // 代码... }];
2)其他属性和方法
// 启动线程 - (void)start; // 取消线程 - (void)cancel; // 暂停线程 + (void)sleepForTimeInterval:(NSTimeInterval)time; // 使当前线程暂停一段时间 + (void)sleepUntilDate:(NSDate *)date; // 或者暂停到某个时刻 // 设置和获取线程名字 - (void)setName:(NSString *)name; - (NSString *)name; // 获取当前线程 + (NSThread *)currentThread; // 获取主线程 + (NSThread *)mainThread;
三、GCD
1.概述
GCD:Grand Central Dispatch。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如:双核、四核等),最重要的是它会自动管理这些线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们程序员去管理,我们只需要告诉干什么就行。同时它使用的是C语言,不过由于使用了Block,使用起来就更加的方便灵活。所以基本上大家都使用GCD这套方案
2.任务&队列
1)任务:即操作,你想要干什么,说白了就是一段代码,在GCD中就是一个Block
a.同步执行(sync):只要是同步执行的任务,都会在当前线程依次执行,不会另开线程,并且会阻塞当前线程,造成GCD的死锁问题(后面会讲到)
b.异步执行(async):只要是异步执行的任务,都会另开线程
2)队列:用于存放任务
a.串行队列:存放当中的任务会依次执行(FIFO)
b.并行队列:存放当中的任务,被依次取出,放到别的线程,然后再取出来一个又放到另一个的线程。这样由于取的动作很快,忽略不计,看起来,所有的任务都是一起执行的。不过需要注意,GCD会根据系统资源控制并行的数量,所以如果任务很多,它并不会让所有任务同时执行
c.主队列:这是一个特殊的串行队列,而且队列中的任务一定会在主线程中执行
3)自由组合

可以看到,同步执行也不一定在主线程,异步执行也不一定是新开线程(考虑主队列)
a.dispatch_sync表示同步执行任务,也就是说在dispatch_sync后,当前队列会阻塞
b.dispatch_sync中的block如果要在当前队列中执行,就是要等待当前队列程序执行完成
#pragma mark - GCD死锁问题 - (void)GCDLock { NSLog(@"只会打印这一句"); // 在主线程中使用同步执行方式 dispatch_sync(dispatch_get_main_queue(), ^{ NSLog(@"这一句不会打印"); }); NSLog(@"这一句也不会打印"); }
主线程在执行dispatch_sync,随后队列中新增一个任务block。因为主队列是同步队列,所以block要等dispatch_sync执行完之后才能执行,但是dispatch_sync是同步派发,要等block执行完才算结束。在主队列中的两个任务互相等待,导致了死锁
#pragma mark - GCD死锁问题 - (void)GCDLock { NSLog(@"这一句会被打印"); dispatch_queue_t queue02 = dispatch_queue_create("testQueue.id", DISPATCH_QUEUE_SERIAL); dispatch_sync(queue02, ^{ NSLog(@"这一句也会被打印"); // 这里就造成死锁 dispatch_sync(queue02, ^{ NSLog(@"这一句不会被打印"); }); NSLog(@"这一句不会被打印"); }); NSLog(@"这一句不会被打印"); }
在queue02串行队列中添加同步任务,就会阻塞当前线程,当前线程中的任务就会去顺序执行,但是不巧的是在当前线程中又添加了一个同步执行任务,这样就造成了死锁
3)解决方案
一般我们也不会使用dispatch_sync.
4.队列组
队列组可以将很多队列添加到一个组里,这样做的好处就是,当这个组里面的所有任务都执行完毕,队列组会通过一个方法通知我们
- (void)test { // 1.创建队列组 dispatch_group_t group = dispatch_group_create(); // 2.创建队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 3.多次使用队列组的方法执行任务, 只有异步方法 // 3.1.执行3次循环 dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 3; i++) { NSLog(@"group-01 - %@", [NSThread currentThread]); } }); // 3.2.主队列执行8次循环 dispatch_group_async(group, dispatch_get_main_queue(), ^{ for (NSInteger i = 0; i < 8; i++) { NSLog(@"group-02 - %@", [NSThread currentThread]); } }); //3.3.执行5次循环 dispatch_group_async(group, queue, ^{ for (NSInteger i = 0; i < 5; i++) { NSLog(@"group-03 - %@", [NSThread currentThread]); } }); // 4.该队列组group中所有的队列任务都执行完毕后会通知调用这个方法 dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSLog(@"完成 - %@", [NSThread currentThread]); }); }
5.常用的代码实例
1)开启子线程异步执行
// 开启子线程异步执行 for (int i = 0; i < 3; i++) { dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSLog(@"%d---%@", i, [NSThread currentThread]); }); }
打印结果:
2017-09-27 10:37:16.468 多线程[1665:111270] 2---<NSThread: 0x608000266300>{number = 5, name = (null)}
2017-09-27 10:37:16.468 多线程[1665:111253] 1---<NSThread: 0x608000266400>{number = 4, name = (null)}
2017-09-27 10:37:16.468 多线程[1665:111269] 0---<NSThread: 0x600000267040>{number = 3, name = (null)}
2)开启队列组异步执行
// 队列组 dispatch_group_t group = dispatch_group_create(); // 获得全局并发队列 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); // 子线程 for (int i = 0; i < 3; i++) { dispatch_group_async(group, queue, ^{ NSLog(@"%d---%@", i, [NSThread currentThread]); }); } // 队列组group全部执行完毕 dispatch_group_notify(group, queue, ^{ NSLog(@"队列组group中任务都已经完成---%@", [NSThread currentThread]); });
打印结果:
2017-09-27 10:42:43.262 多线程[1718:115691] 0---<NSThread: 0x6080000728c0>{number = 3, name = (null)}
2017-09-27 10:42:43.262 多线程[1718:115694] 1---<NSThread: 0x600000079ac0>{number = 4, name = (null)}
2017-09-27 10:42:43.262 多线程[1718:115692] 2---<NSThread: 0x608000072880>{number = 5, name = (null)}
2017-09-27 10:42:43.263 多线程[1718:115692] 队列组group中任务都已经完成---<NSThread: 0x608000072880>{number = 5, name = (null)}
3)回到主线程刷新UI
// 回到主线程 dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"%@", [NSThread currentThread]); });
打印结果:
2017-09-27 10:45:03.104 多线程[1748:117677] <NSThread: 0x608000070e40>{number = 1, name = main}
四、NSOperation
1.概述
NSOperation:和GCD一样,NSOperation也是苹果提供给我们的一套多线程解决方案。实际上他也是基于GCD开发的,但是比GCD拥有更强的可控性和代码可读性。但是需要注意的是,NSOperation是一个抽象类,不可以直接使用,我们往往使用的是它的子类:NSInvocationOperation和NSBlockOperation
2.NSOperation的通用方法
NSOperation *operation = [[NSOperation alloc] init]; // 开始执行 [operation start]; // 取消执行 [operation cancel]; // 执行结束后调用的Block [operation setCompletionBlock:^{ NSLog(@"执行结束"); }];
3.单独使用NSInvocationOperation或者NSBlockOperation--一般不会这么用,毫无意义
1)单独的使用NSInvocationOperation
- (void)testInvocationOperation { // 创建NSInvocationOperation NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethon) object:nil]; // 执行 [invocationOperation start]; } #pragma mark - 耗时操作 - (void)invocationOperationMethon { NSLog(@"%@", [NSThread currentThread]); }
代码打印:
2017-09-27 16:23:32.995 多线程[2540:339704] <NSThread: 0x60800007b640>{number = 1, name = main}
从打印的结果上来看:
NSInvocationOperation其实是同步执行的,因此单独使用的话,这个东西更没有什么卵用,它需要配个我们的队列NSOperationQueue(后面会讲到)
2)单独使用NSBlockOperation
- (void)testBlockOperation { // 创建NSBlockOperation NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1---%@", [NSThread currentThread]); NSLog(@"11111111111111111111111"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"2---%@", [NSThread currentThread]); NSLog(@"2222222222222222"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"3---%@", [NSThread currentThread]); NSLog(@"33333333333333333"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"4---%@", [NSThread currentThread]); NSLog(@"44444444444444444"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"5---%@", [NSThread currentThread]); NSLog(@"555555555555555555"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"6---%@", [NSThread currentThread]); NSLog(@"6666666666666666666"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"7---%@", [NSThread currentThread]); NSLog(@"77777777777777777777777"); }]; // 执行 [blockOperation start]; }
代码打印:
2017-09-27 16:29:30.183 多线程[2609:345650] 1---<NSThread: 0x608000262d00>{number = 1, name = main}
2017-09-27 16:29:30.183 多线程[2609:345650] 11111111111111111111111
2017-09-27 16:29:30.183 多线程[2609:345650] 5---<NSThread: 0x608000262d00>{number = 1, name = main}
2017-09-27 16:29:30.184 多线程[2609:345650] 555555555555555555
2017-09-27 16:29:30.183 多线程[2609:345704] 2---<NSThread: 0x608000270e80>{number = 3, name = (null)}
2017-09-27 16:29:30.184 多线程[2609:345650] 6---<NSThread: 0x608000262d00>{number = 1, name = main}
2017-09-27 16:29:30.184 多线程[2609:345650] 6666666666666666666
2017-09-27 16:29:30.184 多线程[2609:345650] 7---<NSThread: 0x608000262d00>{number = 1, name = main}
2017-09-27 16:29:30.184 多线程[2609:345650] 77777777777777777777777
2017-09-27 16:29:30.183 多线程[2609:345734] 3---<NSThread: 0x600000261a40>{number = 4, name = (null)}
2017-09-27 16:29:30.183 多线程[2609:345735] 4---<NSThread: 0x608000270f40>{number = 5, name = (null)}
2017-09-27 16:29:30.184 多线程[2609:345704] 2222222222222222
2017-09-27 16:29:30.185 多线程[2609:345734] 33333333333333333
2017-09-27 16:29:30.185 多线程[2609:345735] 44444444444444444
从打印结果来看:
a.NSBlockOperation确实是实现了多线程,但是我们可以看到,他并非是将所有的block都放到了子线程中,而是优先将block放到主线程中执行,若主线程已有待执行的代码,就开辟新的线程,但最大并发数是4(包括主线程在内)。如果block数量大于了4,那么剩下的block就会等待某个线程空闲下来之后被分配到该线程,且依然是优先分配到主线程
b.同一个block中的代码是同步执行的
4.NSOperationQueue
针对上面单独使用的种种问题,我们一般会将NSOperation对象添加到NSOperationQueue队列中使用
NSOperationQueue:执行NSOperation的队列,我们可以将一个或多个NSOperation对象放到队列中去执行
放入队列中的NSOperation对象不需要调用start方法,NSOperationQueue会在合适的时机去自动调用
1)NSOperationQueue + NSInvocationOperation
- (void)testOperationQueueWithInvocationOperation { // 创建NSInvocationOperation NSInvocationOperation *invocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(invocationOperationMethon) object:nil]; // 将NSInvocationOperation对象放进NSOperationQueue队列中 // 自定义队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 主队列 // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; [queue addOperation:invocationOperation]; } #pragma mark - 耗时操作 - (void)invocationOperationMethon { NSLog(@"%@", [NSThread currentThread]); }
代码打印:
2017-09-27 16:39:01.784 多线程[2681:353746] <NSThread: 0x608000262cc0>{number = 3, name = (null)}
从打印结果上来看:
这才是真正意义上实现了NSInvocationOperation异步执行且都在子线程中执行
2)NSOperationQueue + NSBlockOperation
- (void)testOperationQueueWithBlockOperation { // 创建NSBlockOperation NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{ NSLog(@"1---%@", [NSThread currentThread]); NSLog(@"11111111111111111111111"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"2---%@", [NSThread currentThread]); NSLog(@"2222222222222222"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"3---%@", [NSThread currentThread]); NSLog(@"33333333333333333"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"4---%@", [NSThread currentThread]); NSLog(@"44444444444444444"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"5---%@", [NSThread currentThread]); NSLog(@"555555555555555555"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"6---%@", [NSThread currentThread]); NSLog(@"6666666666666666666"); }]; // 添加任务 [blockOperation addExecutionBlock:^{ NSLog(@"7---%@", [NSThread currentThread]); NSLog(@"77777777777777777777777"); }]; // 将NSBlockOperation对象放进NSOperationQueue队列中 // 自定义队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 主队列 // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; [queue addOperation:blockOperation]; }
代码打印:
2017-09-27 16:42:20.803 多线程[2724:357054] 1---<NSThread: 0x608000263580>{number = 3, name = (null)}
2017-09-27 16:42:20.803 多线程[2724:357053] 4---<NSThread: 0x600000269080>{number = 6, name = (null)}
2017-09-27 16:42:20.803 多线程[2724:357070] 2---<NSThread: 0x600000268e40>{number = 4, name = (null)}
2017-09-27 16:42:20.803 多线程[2724:357056] 3---<NSThread: 0x608000263880>{number = 5, name = (null)}
2017-09-27 16:42:20.804 多线程[2724:357054] 11111111111111111111111
2017-09-27 16:42:20.804 多线程[2724:357053] 44444444444444444
2017-09-27 16:42:20.804 多线程[2724:357070] 2222222222222222
2017-09-27 16:42:20.804 多线程[2724:357056] 33333333333333333
2017-09-27 16:42:20.805 多线程[2724:357054] 5---<NSThread: 0x608000263580>{number = 3, name = (null)}
2017-09-27 16:42:20.806 多线程[2724:357070] 7---<NSThread: 0x600000268e40>{number = 4, name = (null)}
2017-09-27 16:42:20.806 多线程[2724:357053] 6---<NSThread: 0x600000269080>{number = 6, name = (null)}
2017-09-27 16:42:20.807 多线程[2724:357070] 77777777777777777777777
2017-09-27 16:42:20.807 多线程[2724:357054] 555555555555555555
2017-09-27 16:42:20.808 多线程[2724:357053] 6666666666666666666
从打印结果上来看:
我们看到的NSBlockOperation中的每一个Block也是异步执行且都在子线程中执行,每一个Block内部也依然是同步执行
5.更简单明了的方法
- (void)testOperationQueue { // 自定义队列 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; // 主队列 // NSOperationQueue *mainQueue = [NSOperationQueue mainQueue]; [queue addOperationWithBlock:^{ // 这里是你想做的操作 }]; }
6.依赖关系
NSOperationQueue最吸引人的无疑就是它的添加依赖功能
举例:A依赖于B,那么B在执行结束之前,A永远不会执行
- (void)testOperationQueueWithDependency { // 创建NSInvocationOperation对象--oneInvocationOperation NSInvocationOperation *oneInvocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(oneInvocationOperationMethon) object:nil]; // 创建NSInvocationOperation对象--twoInvocationOperation NSInvocationOperation *twoInvocationOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(twoInvocationOperationMethon) object:nil]; // 添加依赖(oneInvocationOperation依赖于twoInvocationOperation) [twoInvocationOperation addDependency:oneInvocationOperation]; // 添加到队列中 NSOperationQueue *queue = [[NSOperationQueue alloc] init]; [queue addOperation:oneInvocationOperation]; [queue addOperation:twoInvocationOperation]; } #pragma mark - 耗时任务 // 1.第一个任务 - (void)oneInvocationOperationMethon { NSLog(@"one---%@", [NSThread currentThread]); } // 2.第二个任务 - (void)twoInvocationOperationMethon { NSLog(@"two---%@", [NSThread currentThread]); }
代码打印:
2017-09-27 16:47:01.774 多线程[2774:360825] one---<NSThread: 0x608000075b80>{number = 3, name = (null)}
2017-09-27 16:47:01.775 多线程[2774:360808] two---<NSThread: 0x6080000762c0>{number = 4, name = (null)}
从打印结果可以看出:
第二个任务依赖于第一个任务,所以第二个任务必须在第一个任务执行完毕之后才能够执行
使用依赖关系有三点需要注意
a.不要建立循环依赖,会造成死锁,原因同循环引用
b.使用依赖建议只使用NSInvocationOperation。NSInvocationOperation和NSBlockOperation混用会导致依赖关系无法正常实现
c.依赖关系不光在同队列中生效,不同队列的NSOperation对象之前设置的依赖关系一样会生效
d.添加依赖的代码必须放在添加队列的代码前面,因为前面讲到:放入队列中的NSOperation对象不需要调用start方法,NSOperationQueue会在合适的时机去自动调用。如果依赖的代码必须在添加队列之后,我们根本就无法知道在添加依赖的两个任务是否已经执行完毕,如果执行完毕,那么添加依赖也变得毫无意义
7.NSOperationQueue的其他属性
1)NSOperationQueue提供暂停和取消两种操作
暂停:只需要设置NSOperationQueue的suspended属性为YES或NO即可
取消:可以调用NSOperation的的cancle方法取消当前线程,或者调用NSOperation的cancelAllOperations方法来取消该队列中的所有线程
这里要提醒的是:所谓的暂停和取消并不会立即暂停或取消当前操作,而是不再调用新的NSOperation
2)改变NSOperationQueue的最大并发数
改变NSOperationQueue的maxConcurrentOperationCount变量值
这里要提醒的是:最大并发数是有上限的,即使你设置为100,它也不会超过其上限,而这个上限的数目也是由具体运行环境决定的。设置最大并发数一定要在NSOperationQueue初始化后立即设置,因为上面说过,被放到队列中的NSOperation对象是由队列自己决定何时执行的,有可能你这边一添加立马就被执行。因此要想让设置生效一定要在初始化后立即设置

浙公网安备 33010602011771号