多线程相关

多线程的解决方案

GCD、NSOperation、NSThread,performSelectorInBackground: withObject:也可以开启一个异步线程

GCD

  • 同步/异步和串行/并发
    ① 同步串行 dispatch_sync(serial_queue, ^{...});
    ② 异步串行 dispatch_async(serial_queue, ^{...});
    ③ 同步并行 dispatch_sync(concurrent_queue, ^{...});
    ④ 异步并行 dispatch_async(concurrent_queue, ^{...});
  • 死锁产生的原因
    队列引起的循环等待
  • 思考
    问题一:
- (void)viewDidLoad {
   [super viewDidLoad];
   //会产生死锁
   dispatch_sync(dispatch_get_main_queue(), ^{
       [self doSomething];
   });
   //改为异步线程,可以解决死锁
   dispatch_async(dispatch_get_main_queue(), ^{
       [self doSomething];
   });
   //不会产生死锁
   dispatch_queue_t serialQueue = dispatch_queue_create("com.test.serialQueue", DISPATCH_QUEUE_SERIAL);
   dispatch_sync(serialQueue, ^{
       [self doSomething];
   });
}

这段代码会产生死锁死锁的原因是因为viewDidLoad本就在主线程dispatch_get_main_queue()viewDidLoad的结束依赖于dispatch_sync线程的完成,而dispatch_sync被加入dispatch_get_main_queue()dispatch_sync线程的完成需要viewDidLoad的完成,导致了互相等待,从而产生死锁
如果将dispatch_sync改为dispatch_async,由于是异步添加,所以不会阻塞主线程来等待[self doSomething]的执行完成,也就不存在与viewDidLoad的互相等待了。

问题二:

- (void)viewDidLoad {
   [super viewDidLoad];
   NSLog(@"1");
   dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       NSLog(@"2");
       dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
           NSLog(@"3");
       });
       NSLog(@"4");
   });
   NSLog(@"5");
}

这段代码输出结果为

2020-04-12 00:17:17.039572+0800 test[14894:3002426] 1
2020-04-12 00:17:17.039684+0800 test[14894:3002426] 2
2020-04-12 00:17:17.039779+0800 test[14894:3002426] 3
2020-04-12 00:17:17.039874+0800 test[14894:3002426] 4
2020-04-12 00:17:17.039969+0800 test[14894:3002426] 5

因为是并发执行,所以并不依赖于上一个线程是否结束,也就不会产生死锁,又因为是同步执行,所以输出结果为顺序输出。
如果将异步队列改为串行队列,就会同问题一一样,产生死锁
问题三:

情况 ①:

- (void)viewDidLoad {
   [super viewDidLoad];
   dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       NSLog(@"1");
       [self performSelector:@selector(pringLog) withObject:nil afterDelay:0];
       NSLog(@"3");
   });
}
- (void)pringLog{
   NSLog(@"2");
}

这段代码输出结果为

2020-04-12 00:30:51.146398+0800 test[14937:3013405] 1
2020-04-12 00:30:51.146624+0800 test[14937:3013405] 3

performSelector:withObject: afterDelay:方法意为在当前Runloop中延迟0秒后执行selector中方法。 使用该方法需要注意以下事项: 在子线程中调用performSelector: withObject: afterDelay:默认无效,这是因为performSelector: withObject: afterDelay:是在当前Runloop中延时执行的,而子线程的Runloop默认不开启,因此无法响应方法。

针对情况①,修改为情况 ②:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSLog(@"1");
      [self performSelector:@selector(pringLog) withObject:nil afterDelay:0];
      [[NSRunLoop currentRunLoop] run];
      NSLog(@"3");
  });

加上[[NSRunLoop currentRunLoop] run];后会正常输出:

2020-04-12 00:55:23.335976+0800 test[15109:3037849] 1
2020-04-12 00:55:23.336468+0800 test[15109:3037849] 2
2020-04-12 00:55:23.336585+0800 test[15109:3037849] 3

情况③:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSLog(@"1");
      [self performSelector:@selector(pringLog)];
      NSLog(@"3");
  });

如果改为[self performSelector:@selector(printInfo)];则会产生死锁,原因是performSelector:方法会在当前线程同步执行pringLog方法。

情况④:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      NSLog(@"1");
      [self performSelectorInBackground:@selector(pringLog) withObject:nil];
      NSLog(@"3");
  });

使用performSelectorInBackground:withObject:方法输出结果为:

2020-04-12 01:05:43.912294+0800 test[15135:3046984] 1
2020-04-12 01:05:43.912486+0800 test[15135:3046984] 3
2020-04-12 01:05:43.920925+0800 test[15135:3046990] 2
  • dispatch_barrier_async
    dispatch_barrier_async(concurrent_queue, ^{写操作});
    通过GCD实现多读单写,多读单写要实现:
    ① 读者、读者并发;
    ② 读者、写者互斥;
    ③ 写者、写者互斥
    栅栏原理
@interface UesrCenter()
@property (nonatomic, strong) NSMutableDictionary *userCenterDict;
@property (nonatomic, strong) dispatch_queue_t cuncurrent_queue;
@end
@implementation UesrCenter
- (instancetype)init
{
   self = [super init];
   if (self) {
       self.cuncurrent_queue = dispatch_queue_create("com.userCenter.witer.queue", DISPATCH_QUEUE_CONCURRENT);
       self.userCenterDict = [NSMutableDictionary dictionary];     
   }
   return self;
}
- (id) objectForKey:(NSString *)key{
   __block id obj;
   //同步读取指定数据
   dispatch_sync(self.cuncurrent_queue, ^{
       obj = [_userCenterDict objectForKey:key];
   });
   return obj;
}
- (void)setObject:(id)obj forKey:(NSString *)key{
   //异步栅栏调用设置数据
   dispatch_barrier_async(self.cuncurrent_queue, ^{
       [self.userCenterDict setObject:obj forKey:key];
   });
}
  • dispatch_group
    实现A、B、C三个任务并发执行,完成后执行任务D;
@interface GCDGroupTest()
@property (nonatomic, strong) NSArray <NSURL *> *urlArray;
@property (nonatomic, strong) dispatch_queue_t queue_t;
@end
@implementation GCDGroupTest
- (instancetype)init
{
   self = [super init];
   if (self) {
       self.urlArray = [NSArray arrayWithObject:[NSURL URLWithString:@"***.png"]];
       self.queue_t = dispatch_queue_create("CGDGroupTest.queue", DISPATCH_QUEUE_CONCURRENT);
   }
   return self;
}
- (void)handle{
   dispatch_group_t group = dispatch_group_create();
   for (NSURL *url in self.urlArray){
       dispatch_group_enter(group);//手动在group中加入一个任务
       dispatch_group_async(group, self.queue_t, ^{
       NSURLSessionDataTask *task = [[NSURLSession sharedSession] dataTaskWithURL:[NSURL URLWithString:@"https://www.baidu.com"] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
           NSLog(@"任务%@结束", url);
           dispatch_group_leave(group);//手动在group中移除一个任务;
       }];
       [task resume];
     });
     //这里需要注意dispatch_group_async(group,queue,^{...})的结束是以block完成为结束
     //如果block中包含一些异步下载等操作,可能并不会达到预期的效果
     //所以需要手动dispatch_group_enter(group)和dispatch_group_leave(group)来控制任务是否完成
   }
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       //队列group都执行完成之后,在dispatch_get_main_queue线程执行任务
       NSLog(@"全部下载完成");
   });
}
@end

NSOperation

NSOperation需要和NSOperationQueue配合使用来实现多线程方案,在没有使用 NSOperationQueue的情况下,单独使用NSOperation期操作操作是在当前线程执行的,并不会开启新线程。

  • 优势及特点
    ① 添加任务依赖(是GCD和NSThread所不具备的):
    ② 任务执行状态控制:需要创建子类,重写- (void)main- (void)start方法
    NSOperationQueue可以控制最大并发量maxConcurrentOperationCount:默认情况下为-1,表示不进行限制,设为1相当于串行
    ④ 使用KVO观察对操作执行状态的更改:如isExecuteingisFinishedisCancelled
    ⑤ NSOperation可添加完成代码块,在操作完成后执行completionBlock

  • 任务执行状态控制
    ① 如果只重写main方法,底层控制任务状态;
    ② 如果重写了start方法,需要我们自行控制任务状态。
    系统通过KVO移除一个isFinished = YESoperation;

NSThread

结合Runloop来实现常驻线程

多线程与锁

iOS 实现线程加锁有很多种方式。@synchronizedNSRecursiveLockNSLockOSSpinLockNSConditionNSConditionLockpthread_mutexdispatch_semaphore_t
常用的有@synchronizedNSLockOSSpinLock
@synchronized:一般在单例的时候使用,保证创建单例对象时在多线程情况下也是唯一的;
OSSpinLock:自旋锁,循环等待询问,不释放当前资源,主要用于轻量级数据访问,如简单的int值+1/-1操作;
NSLock:需要注意死锁问题,上锁和解锁需要成对出现

- (instancetype)init
{
   self = [super init];
   if (self) {
       self.aLock = [[NSLock alloc] init];
       [self methodA];
   }
   return self;
}
- (void)methodA{
   [self.aLock lock];
   NSLog(@"methodA");
   [self methodB];
   [self.aLock unlock];
}
- (void)methodB{
   [self.aLock lock];
   NSLog(@"methodB");
   [self.aLock unlock];
}

针对上诉NSLock出现的死锁问题,可以使用NSRecursiveLock解决
NSRecursiveLock主要用于方法的递归调用或如上面A->B方法调用,在内部都需要上锁的情况

   ...
   self.aLock = [[NSRecursiveLock alloc] init];
   ...

总结

GCD:用来实现一些简单的线程同步、子线程的分派,多读单写等
NSOperation:可以方便我们管理任务的状态,添加/移除依赖等
NSThread:主要是要实现常驻线程;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • NSThread 第一种:通过NSThread的对象方法 NSThread *thread = [[NSThrea...
    攻城狮GG阅读 871评论 0 3
  • 本文用来介绍 iOS 多线程中 GCD 的相关知识以及使用方法。这大概是史上最详细、清晰的关于 GCD 的详细讲...
    花花世界的孤独行者阅读 530评论 0 1
  • 主队列 细心的同学就会发现,每套多线程方案都会有一个主线程(当然啦,说的是iOS中,像 pthread 这种多系统...
    京北磊哥阅读 393评论 0 1
  • 在这篇文章中,我将为你整理一下 iOS 开发中几种多线程方案,以及其使用方法和注意事项。当然也会给出几种多线程的案...
    张战威ican阅读 635评论 0 0
  • 一、知识结构分析 多线程之间的关系 pthread是POSIX线程的API NSThread是Cocoa对pthr...
    huoshe2019阅读 517评论 0 4