一 多线程
二 NSThread
三 NSOperationQueue
四 GCD
五 多线程管理
一 多线程
1 术语
程序:由源代码生成的可执行应用。(例如:QQ.app)
进程:一个正在运行的程序可以看做一个进程,进程拥有独立运行所需要的全部资源, 每个进程,都在自己独立的内存空间运行,并且进程之间互不影响
线程:程序中独立运行的代码段
进程和线程关系 : 一个进程是由一个或多个线程组成。进程只负责资源的调度和分配,线程才是程序真正的执行单元,负责代码的执行。 进程的所有任务,都是在线程执行的,可以说它是进程的基本执行单元。
串行: 单个进程中执行的任务,是串行,每个任务都是按顺序执行的
单线程: 单个线程,同一时间内,只能执行一个任务
主线程 :每个正在运行的程序(既进程),至少包含一个线程,这个线程叫主线程 。主线程在程序启动时被创建,用于执行 main 函数
单线程程序 : 只有一个主线程的程序,称作单线程程序
单线程程序特点:在单线程程序中,主线程负责执行程序的所有代码(UI展现以及刷新,网络请求,本地存储等等),这些代码只能顺序执行,无法并发执行。
多线程程序:拥有多个线程的程序。
子线程:iOS允许用户自己开辟新的线程,相对于主线程来讲,这些线程,就是子线程。开发者可以根据需要开辟若干子线程。
子线程和主线程特点:都是独立的运行单元,各自的执行互不影响,因此能够并发执行。
3进程和线程的比较:
进程 :进程是 CPU 分配资源和调度的单位; 同一个进程中的线程,共享该进程下的资源
线程 : 线程是 CPU 调用(执行任务)的最小单位; 一个进程可以有多个线程
4 单、多线程区别:
单线程程序:只有一个线程,既主线程,代码顺序执行,容易出现代码阻塞(页面假死)。
多线程程序:有多个线程,线程间独立运行,能有效的避免代码阻塞,并且提高程序的运行性能
4.1 多线程
a 概念:一个进程可以有多个线程,每个线程可以并行(同时)执行不同的任务(下载一个软件,可以使用三个线程,同时下载不同的文件)
b 原理
b.1 统一时间,CPU只能调用一条线程,也就是说同一时间只有一条线程在工作
b.2并行,就是CPU(特指单核)以最快的速度,在不同线程之间进行切换、调度,从而造成了并行的假🐘
b.3 多核CPU,每个核心都可以同时处理不同任务,从而真正达到了多线程并发执行任务
c 多线程优缺点:
c.1优点
c.1.1提高程序运行效率
c.1.2提高了资源利用率,充分使用了空间的内存和CPU资源
c.2 缺点
c.1优点
c.1.1提高程序运行效率
c.1.2提高了资源利用率,充分使用了空间的内存和CPU资源
c.2 缺点
c.2.1 每创建一条线程,都会开辟新的内存空间,并且消耗大约90毫秒的时间
c.2 2 如果开辟了过多的线程,势必会拖慢了 CPU 的运行速度,降低程序性能,让CPU开销过大
c.2.3 多线程设计上往往有些困难,线程间的切换、主线程子线程的协调,都是开发中较难解决的问题
4.2多线程在iOS 开发中的应用
1 主线程
1.1 应用程序启动之后,在UIApplication 中会自动开启一条主线程(UI线程)
1.2 主线程的作用,主要是用来显示/刷新UI界面,处理UI事件
2 使用时的注意事项
2.1 耗时的操作(加载事件过长的操作),不要放在主线程里,否则会影响主线程处理UI事件,导致运行界面拖慢,降低用户体验
2.2 当耗时操作还没有结束时,UI界面无法响应用户的交互,直到耗时操作结束后,主线程才能继续处理UI事件
1.1 应用程序启动之后,在UIApplication 中会自动开启一条主线程(UI线程)
1.2 主线程的作用,主要是用来显示/刷新UI界面,处理UI事件
2 使用时的注意事项
2.1 耗时的操作(加载事件过长的操作),不要放在主线程里,否则会影响主线程处理UI事件,导致运行界面拖慢,降低用户体验
2.2 当耗时操作还没有结束时,UI界面无法响应用户的交互,直到耗时操作结束后,主线程才能继续处理UI事件
2.3 因此:耗时操作,应该放在子线程中去执行(例如后台线程)
注意:iOS 中关于UI的添加和刷新必须在主线程中操作
二 iOS 平台下的多线程---------- iOS 多线程实现种类
1 NSThread
2 NSOperationQueue
3 NSObject
4 GCD
三 NSThread
1 NSThread
优点 : 轻量级的多线程
缺点 : 需要自己管理线程的生命周期,线程同步
2 创建方法
2.1 NSThread的几个属性
currentThread 获取当前线程
mainThread 获取主线程
isMainThread 判断是否为主线程,返回BOOL 值
sleepForTimeInterval 线程休眠时间
2.2 创建子线程的方法
a :NSObject开辟子线程(在后台执行某个方法)
performSelectorInBackground:@selector(SayHi) withObject:nil
参数
performSelectorInBackground 需要使用子线程执行的方法
withObject 传递的参数
b:NSThread手动开辟子线程
//b1 创建线程(NSThread)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(SayHi) object:nil];
//b2 开启线程
[thread start];
//b3 关闭线程(可写可不写)
[NSThread exit];
//b4 取消线程(实际上就是做了标记,表示被取消了)
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(SayHi) object:nil];
//b2 开启线程
[thread start];
//b3 关闭线程(可写可不写)
[NSThread exit];
//b4 取消线程(实际上就是做了标记,表示被取消了)
[thread cancel];
C:NSThread自动开辟子线程
//延迟执行的方法
[NSThread sleepForTimeInterval:5];
//开辟
[NSThread detachNewThreadSelector:@selector(SayHi) toTarget:self withObject:nil];
参数 同 上
在SayHi里面执行如下方法
[self performSelectorOnMainThread:@selector(onMainThread) withObject:nil waitUntilDone:NO];
参数
//使用 NSObject 回到主线程
/*
performSelectorOnMainThread:回到主线程之后需要写的方法
withObject: 传递的参数
waitUntilDone: 判断 方法 是否执行完毕
YES:先执行方法,在回到该函数,执行该函数后面的语句
NO:先执行函数和函数后面的语句 ,后执行方法
/*
performSelectorOnMainThread:回到主线程之后需要写的方法
withObject: 传递的参数
waitUntilDone: 判断 方法 是否执行完毕
YES:先执行方法,在回到该函数,执行该函数后面的语句
NO:先执行函数和函数后面的语句 ,后执行方法
*/
注意 :
1 每个线程都维护着与自己对应的 NSAutoreleasePool 对象,将其放在线程栈的栈顶。当线程结束时候,会清空自动释放池
2 为保证对象的及时释放,在多线程方法中需要添加自动释放池
3 在应用程序打开的时候,系统会自动为主线程创建一个自动释放池
4 我们手动创建的子线程需要我们手动添加自动释放池
四 NSObject 实现异步后台执行
1 NSObject 开辟线程
a :NSObject开辟子线程(在后台执行某个方法)
performSelectorInBackground:@selector(SayHi) withObject:nil
参数
performSelectorInBackground 需要使用子线程执行的方法
withObject 传递的参数
五 NSOperation 和 NSOperationQueue
1 NSOperation 类
1.1 首先,在MVC中属于M,是用来封装单个任务相关的代码和数据的抽象类,
1.2他是一个抽象类,不能直接使用这个类。所以执行任务的是它的子类:
NSInvocationOperation和 NSBlockOperation .这两个子类,相当于一个方法选择器“prefromSelector()”,由它两本身发起的任务,并不是在子线程中执行
1.3 NSOperation 和它的子类,本身并不会进行线程的创建,所以,在他们的任务方法中打印当前线程,显示为主线程
1.4 NSOperation 和它的子类,只是一个操作,本身没有主线程、子线程之分,可以在任何线程中使用,通常和NSOperationQueue 结合使用
1.5 代码 NSOperation本身和多线程没有任何关系,它只是封装了一定的代码段和数据去实现一个功能
1.5.1 NSInvocationOperation封装了执行操作的target 和要执行的action
NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(SayHe) object:nil]; //NSInvocationOperation 对象在单独使用的时候,需要手动调用开启方法
//// [operation start];
1.5.2 NSBlockOperation 封装了需要执行的代码块
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
// NSLog(@"%@",[NSThread currentThread]);
// }]; // //开启
//// [blockOperation start];
注意:NSOperationQueue 创建多线程,如果搭配了 NSOperationQueue 中的 add 方法创建多线程的话,就不需要使用 Start 方法,否则会崩溃
2 NSOperationQueue
2.1一个 NSOperationQueue 操作队列,就相当于一个线程管理器,管理一组Operation对象的执行,将 NSOperation 和子类的对象放入队列中,然后由队列负责派发任务,所以 NSOperationQueue 并不是一个线程。但是,你可以设置队列中运行的线程的数量
2.2 其中 NSOperation可以调节它在队列中的优先级(使用 addDependency:设置依赖关系)
2.3当最大并发数设置为 1 的时候,能实现线程同步(串行执行)
----优点-----
1 不需要手动关联线程,只需要把精力放在自己要执行的操作上面
----缺点-----
1 不需要手动关联线程,只需要把精力放在自己要执行的操作上面
----缺点-----
1 它是基于 OC对象,那么相对于 C函数来说,效率要低,而且基于 GCD,那么GCD 提供的功能比它更全面
2.4 代码实现 NSOperationQueue 操作队列,就相当于一个线程管理器, 所以它可以根据Operation 任务自己分配任务,自己管理线程的声明周期,而且,用NSOperationQueue 创建的,是 N个并行队列
a //初始化
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
b //设置最大并发数
//当设置最大并发数为1 时,也可叫串行,顺序执行
//当设置最大并发数大于1时,叫并行,多条通道同时进行,互不影响
queue.maxConcurrentOperationCount = 1;
b //设置最大并发数
//当设置最大并发数为1 时,也可叫串行,顺序执行
//当设置最大并发数大于1时,叫并行,多条通道同时进行,互不影响
queue.maxConcurrentOperationCount = 1;
C //测试最大并发数
for (int i = 0; i<10; i++) {
NSBlockOperation *block = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%@",[NSThread currentThread]);
}];
[queue addOperation:block];
[queue addOperation:block];
}
六 GCD (Grand Central Dispatch)
是Apple开发的一种多核编程技术,主要用于优化应用程序以支持多核处理器以及其他对称多处理系统,GCD提供函数实现多线程开发,性能更强大。
1、特点:
1.1 纯C语言编写,所以在使用的时候,使用的是函数而不是方法。
1.2 GCD可以充分利用多核硬件并发处理多个任务,也就是说,效率高。
1.3 GCD使用后,不用程序去管理线程的开闭,GCD会在系统城上面动态的检测系统状态,开闭线程。
1.4 管理线程的生命周期(调度任务、销毁线程、创建线程)
1.1 纯C语言编写,所以在使用的时候,使用的是函数而不是方法。
1.2 GCD可以充分利用多核硬件并发处理多个任务,也就是说,效率高。
1.3 GCD使用后,不用程序去管理线程的开闭,GCD会在系统城上面动态的检测系统状态,开闭线程。
1.4 管理线程的生命周期(调度任务、销毁线程、创建线程)
2、核心概念:
2.1 任务:执行什么操作(具有一定功能的代码段,一般是一个block或者函数)
2.2 分发队列:用来存放任务(GCD以队列的方式进行工作,FIFO)
2.3 GCD 会根据分发队列的类型,创建合适数量的线程执行队列中的任务
3、使用GCD的两个步骤
3.1 定制任务:确定需要做什么事情
3.2 将任务添加到队列中:
3.2.1 GCD会自动将队列中的任务取出,放到对应的线程中。
3.2.2 而任务的取出,遵循队列的先进先出(FIFO)原则。
4、队列
4.1 并发队列
4.1.1 可以让多个任务并发(同时)执行(自动开启了多个线程,同时执行任务)
4.1.2 并发功能只能在异步函数下才会有效
4.2 串行队列:让任务一个接一个执行(一个完成,执行下一个)
4.3 并发和串行,决定了任务的执行方式
4.3.1 并发:多个任务 同时 执行
4.3.2 串行:多个任务 挨个 执行
4.3 dispatch queue 分两种:
SerialQueue :一次执行一个任务。 Serial Queue 通常用于特定的资源或数据。当你创建多个Serial queue 时,虽然它们各自是同步执行的,但 Serial queue 与 Serial queue之间是并发执行的。SerialQueue 能实现线程同步
Concurrent :可以并发的执行多个任务,但是遵守 FIFO
5 代码
5.1 获取串行队列代码
第一种:系统提供的串行队列创建方式(串行队列中比较实用的,常用在开发中
dispatch_queue_t queue1 = dispatch_get_main_queue();//主队列
里面的任务在主线程依次去执行
同步: 同步执行任务:在一个线程当中,让事情有序的执行(只能在当前线程中执行,不能开辟新线程)
dispatch_sync(queue2, ^{
NSLog(@" %@",[NSThread currentThread]);
});
异步: 异步执行任务:在另一个线程中执行(可以在新线程中开启任务,具有开辟新线程的能力)
dispatch_async(queue2, ^{
NSLog(@" %@",[NSThread currentThread]);
});
注:一般都会使用异步开辟新的线程去执行任务
//第二种:自己创建的队列,第一个参数是队列的名字(苹果推荐使用反向域名去命名),第二个参数队列的类型(串行队列、并行队列),这种方式创建的队列,它会自己去开辟一个子线程去完成队里面的任务。
dispatch_queue_t queue2 = dispatch_queue_create(“com.lanou3g.mySeria", DISPATCH_QUEUE_SERIAL);
注:队列类型可以写0 ,系统默认为 串行
5.1 获取并列队列代码
//第一种:系统方法创建并发队列(是苹果了里面的全局队列,有四个优先级)
第一个参数就是队列的优先级,第二个参数是苹果预留的参数为了以后去使用,目前没有用到,填写0
dispatch_queue_t queueS = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//第二种:自己创建并发队列 参数同上
dispatch_queue_t queueM = dispatch_queue_create("queueM", DISPATCH_QUEUE_CONCURRENT);
5.3 延迟执行一段代码
dispatch_after(
参数一:dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC))表示:【计算时间(从现在开始计时,(int64_t)(真正延迟的时间 * NSEC_PER_SEC))】
参数二:dispatch_get_main_queue(), ^{
任务;
任务;
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"已经3秒了");
NSLog(@"已经3秒了");
});
5.4 GCD功能
5.4.1 dispatch_async() // 往队列中添加任务,任务会排队执行
参数同上(异步)
5.4.2dispatch_after()//往队列中添加任务,任务不但不会排队,还会在延迟的时间点执行
参数同上
5.4.3dispatch_apply()//往队列中添加任务,任务会重复执行 n 次
dispatch_apply
(
参数一:size_t iterations:执行的次数
参数二:dispatch_queue_t queue:队列(在哪个队列里面去执行)
参数三:^(size_t 写一个索引) {
参数4 :任务
});
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"index__%zu",index);
*/
dispatch_apply(10, queue, ^(size_t index) {
NSLog(@"index__%zu",index);
});
5.4.4 dispatch_group_async()//将任务添加到队列中,并添加分组标记
a dispatch_group_t group = dispatch_group_create();
作用:主要用于把一些不相关的任务归为一组,组里面放的是队列
b dispatch_queue_t queue = dispatch_queue_create("queue", DISPATCH_QUEUE_CONCURRENT);
c dispatch_group_async(group, queue, ^{
作用:向分组中的队列添加任务
NSLog(@"我是第一个任务");
});
d dispatch_group_notify(group, queue, ^{
NSLog(@"无论如何,我都是最后一个");
});
注意:往分组里面的队列添加任务,最少要添加一个任务。否则,notify里面的任务不会等待小组里面的其他任务执行完才执行
5.4.5 dispatch_group_notify()//将任务添加到队列中,当某个分组的所有任务完成之后,此任务才会执行
作用: 监听组里面的任务,等到组里面的任务全部执行完成之后,才会执行它里面的任务
5.4.6 dispatch_barrier_async()//将任务添加到队列中,此任务执行的时候,其他任务停止执行
作用:在它之前和之后的任务都可以并发的去执行
5.4.7 dispatch_once()//任务添加到队列中,但任务在程序运行过程中,只执行一次
作用:该函数接收一个dispatch_once用于检查该代码块是否已经被调度的谓词(是一个长整形,实际上作为BOOL使用)。它还接收一个希望在应用的生命周期内仅仅被调度一次的代码块。
dispatch_once不仅意味着代码仅仅会被运行一次,而且还是线程安全的,这就意味着你不需要使用诸如@synchronized之类的来防止使用多个线程或者队列时不同步的问题
自定义単例
5.4.8 dispatch_sync()//将任务添加到队列中,block不执行完,下面的代码不会执行
5.4.9 dispatch_async_f()//将任务添加到队列中,任务是函数非block
dispatch_async_f(dispatch_queue_t queue, void *context, dispatch_function_t work)
第一个参数:队列
第二个参数:函数参数的内容
第三个参数:函数
5.4.10 async 和sync 的区别
async 不等block 体执行完,就去执行下面的代码
sync 会等待 block 体执行完成之后,才会去执行block体外面的代码
七 线程间通信
1 主线程进入子线程(前面的方法都可以)
2 子线程回到主线程
GCD :dispatch_queue_t queue1 = dispatch_get_main_queue()
NSObject: performSelectorOnMainThread: withObject:waitUntilDone: modes:
3 回到指定线程
八 线程互斥