问题:请简单介绍下iOS的多线程

iOS的三种多线程技术

  • NSThread:每个NSThread对象对应一个线程,量级较轻(真正的多线程)
  • NSOperation/NSOperationQueue 面向对象的线程技术
  • GCD(Grand Central Dispatch) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术

以上这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的,在项目中很多框架技术分别使用了不同多线程技术。

三种多线程技术的对比

NSThread:

  • 优点:NSThread 比其他两个轻量级,使用简单
  • 缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销

NSOperation:

  • 优点:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上
  • 缺点:面向对象的抽象类,只能实现它或者使用它定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

GCD(Grand Central Dispatch):

  • 优点:GCD是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThreadNSOperation的高效和强大的技术。最高效,避开并发陷阱。
  • 缺点:GCD是基于C语言的

选择小结:

  • 简单而安全的选择NSOperation实现多线程即可。
  • 处理大量并发数据,又追求性能效率的选择GCD。
  • NSThread本人选择基本上是在做些小测试上使用,当然也可以基于此造个轮子。

三种多线程技术的实现

NSThread的多线程技术

  1. 静态实例化。类方法直接开启后台线程,并执行选择器方法detachNewThreadSelector
// 新建一个线程,调用@selector方法
[NSThread detachNewThreadSelector:@selector(bigDemo) toTarget:self withObject:nil];
  1. 动态实例化。成员方法,在实例化线程对象之后,需要使用start执行选择器方法initWithTarget
// 成员方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(bigDemo) object:nil]; 
// 启动start线程
[thread start];
  1. 隐式实例化。对于NSThread的简单使用,可以用NSObject的performSelectorInBackground替代
// performSelectorInBackground是将bigDemo的任务放>在后台线程中执行
[self performSelectorInBackground:@selector(bigDemo) withObject:nil];</pre>

同时,在NSThread调用的方法中,同样要使用autoreleasepool进行内存管理,否则容易出现内存泄露。

// 自动释放池 2 
// 负责其他线程上的内存管理,在使用NSThread或者NSObject的线程方法时,一定要使用自动释放池 4 
// 否则容易出现内存泄露。

@autoreleasepool {

}
  1. NSThread的拓展认识
#获取当前线程
NSThread *current = [NSThread currentThread];
#获取主线程
NSThread *main = [NSThread mainThread];   
#暂停当前线程
[NSThread sleepForTimeInterval:2];  

#线程之间通信
#在指定线程上执行操作
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 
#在主线程上执行操作
[self performSelectorOnMainThread:@selector(run) withObject:nil waitUntilDone:YES]; 
#在当前线程执行操作
[self performSelector:@selector(run) withObject:nil]; 

NSOperation,面向对象的多线程技术

一、使用步骤:
  • 1. 实例化操作
// 实例化操作队列
_queue = [[NSOperationQueue alloc] init];

NSInvocationOperation

NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(opAction) object:nil];
// 如果使用start,会在当前线程启动操作
// [op1 start];  
// 1\. 一旦将操作添加到操作队列,操作就会启动
[_queue addOperation:op1];

NSBlockOperation

#pragma mark 模仿下载网络图像
- (IBAction)operationDemo3:(id)sender {
   // 1\. 下载
  NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
      NSLog(@"下载 %@" , [NSThread currentThread]);
  }];
  // 2\. 滤镜
  NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"滤镜 %@" , [NSThread currentThread]);
    }]; 
   // 3\. 显示
   NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
       NSLog(@"更新UI %@" , [NSThread currentThread]); 
   }]; 
   // 添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动   
   // 依赖关系可以跨线程队列实现 
   // 提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
   [op2 addDependency:op1]; 
   [op3 addDependency:op2]; 
//    [op1 addDependency:op3];

   [_queue addOperation:op1]; 
   [_queue addOperation:op2]; 
   [[NSOperationQueue mainQueue] addOperation:op3]; 
}
  • 2. 将操作添加到队列NSOperationQueue即可启动多线程执行
[_queue addOperation:op1]; 
[_queue addOperation:op2];
二、更新UI使用主线程队列
//两方式
[NSOpeationQueue mainQueue] addOperation ^{
};
[[NSOperationQueue mainQueue] addOperation:op3];
三、操作队列setMaxConcurrentOperationCount可以设置同时并发的线程数量!
# 控制同时最大并发的线程数量
[_queue setMaxConcurrentOperationCount:2];
#提示:此功能仅有NSOperation有!
四、使用addDependency可以设置任务的执行先后顺序,同时可以跨操作队列指定依赖关系
//添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动
//依赖关系可以跨线程队列实现。
#提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
[op2 addDependency:op1]; 
[op3 addDependency:op2];
[op1 addDependency:op3];
#提示:在指定依赖关系时,注意不要循环依赖,否则不工作。

GCD,C语言

GCD就是为了在“多核”上使用多线程技术

  1. 要使用GCD,所有的方法都是dispatch开头的
  2. 名词解释
  • global 全局
  • queue 队列
  • async 异步
  • sync 同步
  1. 要执行异步的任务,就在全局队列中执行即可。dispatch_async 异步执行控制不住先后顺序。
  2. 关于GCD的队列
  • 4.1 全局队列 dispatch_get_global_queue
#参数:优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT 始终是 0
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
#可同步 可异步
  • 4.2 串行队列
dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
#
#是创建得到的,不能直接获取!只能同步!
  • 4.3 主队列 dispatch_get_main_queue
dispatch_async(dispatch_get_main_queue(), ^{
   NSLog(@"main - > %@", [NSThread currentThread]); 
});
#只能同歩
  1. 异步和同步与方法名无关,与运行所在的队列有关!
    同步主要用来控制方法的被调用的顺序
  2. 种多线程实现
  • 6.1 后台执行线程创建
dispatch_async(dispatch_get_global_queue(0, 0), ^{
   [self loadImageSource:imgUrl1];
});
  • 6.2 UI线程执行(只是为了测试,长时间加载内容不放在主线程)
dispatch_async(dispatch_get_main_queue(), ^{
   [self loadImageSource:imgUrl1];
});
  • 6.3 一次性执行(常用来写单例)
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
   [self loadImageSource:imgUrl1];
});
  • 6.4 并发地执行循环迭代
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
size_t count = 10;
dispatch_apply(count, queue, ^(size_t i) {
   NSLog(@"循环执行第%li次",i);
   [self loadImageSource:imgUrl1];
});  
  • 6.5 延迟执行
double delayInSeconds = 2.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
   [self loadImageSource:imgUrl1];
});  
  • 6.6 自定义dispatch_queue_t
dispatch_queue_t urls_queue = >dispatch_queue_create("minggo.app.com", NULL);
dispatch_async(urls_queue, ^{
   [self loadImageSource:imgUrl1];
});
  1. 对比多任务执行
    异步加载图片是大部分app都要面对的问题,那么加载图片是按循序加载完之后才刷新UI呢?还是不安顺序加载UI呢?显然大部分的希望各自加载各自的图片,各自刷新。以下就是模拟这两种场景。
  • 7.1 先后执行,加载两张图片为例
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   UIImage *image1 = [self loadImage:imgUrl1];
   UIImage *image2 = [self loadImage:imgUrl2];
   dispatch_async(dispatch_get_main_queue(), ^{
       self.imageview1.image = image1;
       self.imageView2.image = image2;
   });
});  
  • 7.2 并行队列执行,也是以加载两张图片为例
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
   dispatch_group_t group = dispatch_group_create();
   __block UIImage *image1 = nil;
   __block UIImage *image2 = nil;

   dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       image1 = [self loadImage:imgUrl1];
   });
   
   dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       image2 = [self loadImage:imgUrl2];
   });
   
   dispatch_group_notify(group, dispatch_get_main_queue(), ^{
       self.imageview1.image = image1;
       self.imageView2.image = image2;
   });
});

7.1中等到两张图片加载完成后一起刷新,7.2就是典型的异步并行的例子,不需要理会各自图片加载的先后问题,完成加载图片刷新UI即可。从加载图片中来说,第7.1种不太合适使用,但是对于在上边场景选择中的创作工具来说有很大的好处,首先得异步进行,然后异步中有得按顺序执行几个任务,比如上传章节内容。因此,我们可以灵活考虑使用这两多线程任务执行方式,实现各种场景。

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