多线程相关

多线程的解决方案

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:主要是要实现常驻线程;

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容

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