iOS-多线程汇总

CPU不同调度:一个进程可以开启多条线程,每条线程可以并行执行不同的任务。
任务执行完毕依赖于线程池:不一定按照固定的顺序

  • 进程:系统中正在运行的一个应用程序
  • 线程:一个进程要想执行任务,必须得有线程,线程是进程的执行路径
  • 多线程原理:同一时间,CPU只能从事处理1条线程,只有1条线程在工作。多线程并发执行,其实就是CPU快速地在多条线程之间切换(CPU调度线程的时间足够快,就造成了多线程并发执行的假象)
  • 多线程的优缺点:
  • 优点:提高程序的执行效率,提高资源利用率
  • 缺点:线程是有开销的,创建线程大约需要90毫秒的创建时间
  • 缺点:如果开启大量线程,会降低程序的性能。
  • 缺点:线程越多,CPU调度开销越大;
  • 应用:

一个iOS程序运行后,默认会开启一条线程,称为“主线程”或“UI线程”。线程的主要作用:显示/刷新UI;处理UI事件(比如点击、滚动、拖拽事件等。)
使用注意:别将比较耗时的操作放到主线程中;耗时操作会卡住主线程,严重影响UI的流畅度,给用户一种“卡”的坏体验。
比如说:一个操作需要耗时10秒,但是用户在第5秒时点击了按钮就会很卡 5秒后才能执行。

解决:****将耗时操作放在子线程,当用户第五秒点击按钮那一刻就有反应,能同时处理耗时操作和UI控件的事件

一、NSThread

创建和启动线程

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];// 线程一启动,就会在线程thread中执行self的run方法

其他创建线程的方式

//①创建线程后自动启动线程
[NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];
//②隐式创建并启动线程
[self performSelectorInBackground:@selector(run) withObject:nil];//所有继承NSObject的类都可以调用
上述2种创建线程方式的优缺点 优点:简单快捷 缺点:无法对线程进行更详细的设置

控制线程的状态:

NSThread 线程状态:阻塞,就绪,死亡

//①启动线程
- (void)start; //将线程加入到线程池里面-》CPU调度线程  运行状态由CPU控制
//②进入就绪状态 -> 运行状态。当线程任务执行完毕,自动进入死亡状态
阻塞(暂停)线程
+ (void)sleepUntilDate:(NSDate *)date;
+ (void)sleepForTimeInterval:(NSTimeInterval)ti;// ③进入阻塞状态  时间到了从阻塞到就绪状态
强制停止线程 
+ (void)exit;//④进入死亡状态 从线程池中拿出来,销毁   注意:一旦线程停止(死亡)了,就不能再次开启任务

线程通信

在一个进程中,线程往往不是孤立存在的,多个线程之间需要经常进行通信。

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait;
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait;
//①开启子线程:
NSThread * t1 = [[NSThread alloc] initWithTarget:self selector:@selector(downImage) object:nil];[t1 start];
//②回到主线程:
[self performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];//YES 相当于给Timer加了一个Runloop

二、GCD

全称是Grand Central Dispatch,可译为“牛逼的中央调度器”,纯C语言,提供了非常多强大的函数。

苹果公司为多核的并行运算提出的解决方案、自动管理线程的生命周期、只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

核心概念

将任务添加到队列,队列按照程序员指定的方式调度任务,并发和串行主要影响:任务的执行方式。
将任务添加到队列,并指定执行任务的函数

定制任务 -> 将任务添加到队列 -> (GCD会自动将队列中的任务取出,放到对应的线程中执行,任务的取出遵循队列的FIFO原则:先进先出,后进后出)

  • 同步:第一个任务没有结束,就不会执行下一个任务
  • 异步:不用等待任务执行完毕,就会执行下一个任务
  • 并发:允许多个任务同时执行
  • 串行:一个任务执行完毕后,再执行下一个任务

同步执行(sync这一句不执行,就不会执行下一句)

dispatch_queue_t q = dispatch_get_global_queue(0, 0);
void (^tasks)(void) = ^{
    NSLog(@"%@",[NSThread currentThread]);
};
dispatch_sync(q, tasks);

异步执行(如果任务没有完成,可以不等待,异步执行下一个任务[具备开启线程的能力])

dispatch_queue_t q = dispatch_get_global_queue(0, 0);
void (^tasks)(void) = ^{
    NSLog(@"%@",[NSThread currentThread]);
};
dispatch_async(q, tasks);

线程间通信

// 异步执行
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 耗时操作
    NSLog(@"%@",[NSThread currentThread]);
    // 更新UI
    dispatch_async(dispatch_get_main_queue(), ^{
    // 回到主线程,执行UI刷新操作
        NSLog(@"更新UI%@",[NSThread currentThread]);
    });
});

队列

-串行队列:一个接着一个地执行任务
-并发队列:可以同时调度多个任务
-任务执行函数(任务都需要在线程中执行)

并发队列(Concurrent Dispatch Queue)

  • 自动开启多个线程同时执行任务
  • 并发功能只有在异步(dispatch_async)函数下才有效
  • dispatch_get_global_queue 获取全局的并发队列
//GCD默认已经提供了全局的并发队列,供整个应用使用,可以无需手动创建
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);
//优先级
/*
#define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
#define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台
*/

串行队列(Serial Dispatch Queue)

让任务一个接着一个地执行

GCD中获得串行有2种途径
//1.使用dispatch_queue_create函数创建串行队列
dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL); 
//2.使用主队列
dispatch_queue_t queue = dispatch_get_main_queue();

GCD调度组 dispatch_group_t

//1.队列
dispatch_queue_t q = dispatch_get_global_queue(0, 0);
//2.调度组
dispatch_group_t g = dispatch_group_create();
//3.添加任务,将队列调度任务。任务完成的时候,能够提醒
dispatch_group_async(g, q, ^{
    NSLog(@"download A %@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"download B %@",[NSThread currentThread]);
});
dispatch_group_async(g, q, ^{
    [NSThread sleepForTimeInterval:1.0];
    NSLog(@"download C %@",[NSThread currentThread]);
});
dispatch_group_notify(g, dispatch_get_main_queue(), ^{
    //更新UI,通知用户
    NSLog(@"OK %@",[NSThread currentThread]);//1
});
NSLog(@"Come Here ");
dispatch_barrier_async

这个函数要等在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行。(这个queue不能是全局的并发队列)

dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

三、NSOperation

将“操作”添加到“队列”

NSOPeration抽象类 对GCD面向对象的封装,
特点:不能直接使用 目的:定义子类共有的属性和方法.
子类:NSInvocationOperation、NSBlockOperation
-最大并发数
-队列的暂停、继续
-取消所有操作
-提供线程依赖关系(线程A 的执行 必须依赖线程B)

回归主线程:

[self.opQueue addOperationWithBlock:^{
    NSLog(@"%@",[NSThread currentThread]);
    //主线程
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        NSLog(@"Main Thread %@",[NSThread currentThread]);
    }];
}];

提供依赖关系

- (void)dependency {
    /**
     * 下载、解压、通知用户
     */
    //1.下载
    NSBlockOperation * op1 = [NSBlockOperation blockOperationWithBlock:^{
        [NSThread sleepForTimeInterval:3.0];
        NSLog(@"下载--%@ ",[NSThread currentThread]);
    }];
    //2.解压
    NSBlockOperation * op2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"解压--%@ ",[NSThread currentThread]);
    }];
    //3.通知用户
    NSBlockOperation * op3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"通知用户--%@ ",[NSThread currentThread]);
    }];
    NSLog(@"Come here");
    //NSOperation 提供依赖关系
    [op2 addDependency:op1];
    [op3 addDependency:op2];
    //[op1 addDependency:op3];//循环依赖 此时队列不执行工作,也不会崩溃
    //添加队列
    [self.opQueue addOperations:@[op1,op2] waitUntilFinished:YES];// YES在所有任务都完成后才会走Come here.
    //在主线程上通知用户
    [[NSOperationQueue mainQueue] addOperation:op3];
    NSLog(@"Come here");
}

四、信号量

信号量其实就是一个整数值并具有一个初始化的值,有关信号量有两个操作:
信号通知、信号等待

注意
当信号量被信号通知时,其计数会被增加。
当线程在信号量等待时,线程会阻塞,计数会减少

三种重要的函数

dispatch_semaphore_create

创建一个信号量,具有整形的数值,即为信号的总量。

dispatch_semaphore_signal

返回值为long类型,当返回值为0时,表示当前并没有线程等待其处理的信号量,其处理的信号总量增加1。
当返回值不为0时,表示其当前有一个或者多个线程等待其处理的信号量,并且该函数唤醒了一个等待的线程(当线程有优先级的时候,唤醒优先级最高的线程,否则随机唤醒)。

补充:

1.GCD | NSOperation 区别:

//NSOperation 和  GCD 对比:
 //GCD 在 iOS 4.0 推出,针对多核处理器优化的并发技术,是C语言的
     -将任务(block)添加到队列 (串行、并行、主队列、全局队列),并且要指定任务的同步、异步;
     -线程间的通讯 dispatch_get_main_queue()
     -提供了一些  NSOperation 不具备的功能:
       一次执行、 -延迟执行  、-调度组(NSoperation 也可以做到,但有点儿麻烦)
// NSOperation 在 iOS 2.0; 苹果推出了GCD后,对NSOperation 进行了重写
     -将操作[异步执行的任务添加到并发队列],就会立刻异步执行
     -提供了一些 GCD实现起来比较麻烦的功能 【下载】
       -最大并发数
       -队列的暂停、继续
       -取消所有线程
       -线程依赖(线程A 的执行 必须依赖线程B)(GCD 同步实现!)
  • NSThread -> pthread 队列:GCD 并发队列
  • NSOperation -> GCD 操作:异步执行任务

2.多线程隐患:多个线程访问同一个资源,容易引发数据错乱和数据安全问题

==互斥锁==

@synchronized(锁对象self) { // 需要锁定的代码  }

注意:锁定1份代码只用1把锁,用多把锁是无效的
//当Thread A 执行完+1操作后,就把线程锁住,ThreadB无法访问,等到Thread A 用完之后解锁,此时ThreadB才能访问。
能有效防止因抢夺资源造成的数据安全问题 缺点:需要消耗大量CPU资源

互斥&自旋锁🔐

保证共享数据操作的完整性,保证在任一时刻,只能有一个线程访问对象

1.互斥锁
  • 互斥锁:如果发现其他线程正在执行锁定代码,线程就会进入休眠状态,等待线程完成,就唤醒
  • 加了互斥做的代码,当新线程访问时,如果发现其他线程正在执行锁定的代码,新线程就会进入休眠。
2.自旋锁
  • 自旋锁:如果发现其他线程正在执行锁定代码,线程就会死循环模式,等待线程完成,就执行锁定代码
  • 自旋锁的效率高于互斥锁
  • 加了自旋锁,当新线程访问代码时,如果发现有其他线程正在锁定代码,新线程会用死循环的方式,一直等待锁定的代码执行完成。相当于不停尝试执行代码,比较消耗性能

3.原子和非原子性

原子性:线程安全,消耗大量资源(多个线程操作数据,保证线程正确)

非原子性:非线程安全,适合内存小的移动设备(保证同一时间只有一个线程能够写入)

4.线程间通信

回到主线程

①[self performSelectorOnMainThread]

②[[NSOperationQueue mainQueue] addOperationWithBlock:];

③GCD

dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"更新UI%@",[NSThread currentThread]);
        });

5.GCD如何实现线程安全?

分发队列本身是线程安全的。你可以在任意线程中将任务提交到分发队列中,无需给队列加锁或使用同步机制

6.GCD 队列组如何取消其中一个

GCD原生并不支持取消已经加入queue的block

dispatch_block_cancel(^{});
如何实现

正常情况下,GCD的任务一旦开始执行时无法停止的,但是我们可以加一个外部变量,用于标记block是否需要取消。然后block中通过及时检测这个外部变量的状态,当发现需要取消时,停止block中的后续操作。就能达到及时取消block的目的。

7.利用NSOperation与NSOperationQueue处理多线程时,有3个NSOperation分别为A,B,C,要求A,B执行完之后,才执行C,如何做?

A.添加依赖
//创建列队
NSOperationQueue *queue = [NSOperationQueue alloc]init];
//创建3个操作
NSOperation *a = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation1....");
}];

NSOperation *c = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation2....");
}];

NSOperation *b = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"operation3....");
}];
//======添加依赖=======
//只有当a操作执行完毕后,才会执行c操作
[c addDependency:a];
//只有当b操作执行完毕后,才会执行c操作
[c addDependency:b];

[queue addOperation:a];
[queue addOperation:b];
[queue addOperation:c];

B.设置优先级
- (NSOperationQueuePriority)queuePriority;

8.NSOperatinQueue和CGD的区别,什么情况下用NSOperationQueue,什么情况下用GCD

比较

1.GCD是纯C语言的API,NSOperationQueue是基于GCD的OC版本封装
2.GCD只支持FIFO的列队(先进先出),NSOperationQueue可以很方便地调整执行顺序(设置优先级 ),设置最大并发数量
3.NSOperatinQueue可以设置Operation间依赖关系,而GCD需要些很多代码才能实现
4.NSOperationQueue支持KVO,可以监听operation是否正在执行(isExecuted)、是否结束(isFinished),是否取消(isCanceld);系统内部已经做好的KVO
5.GCD的执行速度比NSOperationQueue快

什么时候,用哪个?

1.任务之间有依赖/或者要监听任务的执行情况:NSOperatinQueue(任务需要时刻监听;任务严格需要按顺序执行)
2.任务之间不太相互依赖就用:GCD

9.如何用GCD同步若干个异步调用,请用代码说明?

//获取并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

//获取组
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, queue, ^ {

    NSLog(@ "线程:%@--加载图片1", [NSThread currentThread]);
});

dispatch_group_async(group, queue, ^ {

    NSLog(@ "线程:%@--加载图片2", [NSThread currentThread]);;
});

dispatch_group_async(group, queue, ^ {

    NSLog(@ "线程:%@--加载图片3", [NSThread currentThread]);
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^ {

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

推荐阅读更多精彩内容