多线程笔记
来源:互联网 发布:淘宝未发货算退款率吗 编辑:程序博客网 时间:2024/05/03 04:52
写在最前面 同步是打电话 异步是发短信
一,NSThread
创建和使用方法很简单不写了
1.完善线程的入口
如果新开辟的线程没有 Autorelease Pool 的话,那么在新线程中生成的 Autorelease 对象会存放到主线程的 AutoreleasePool 中,当新开辟的线程被终止时,线程中的 Aurorelease对象不会被最终释放掉,占用主线程资源.所以我们需要在线程的入口处创建一个 AuroreleasePool,当线程退出的时候释放这个Autorelease Pool.这样在线程中创建的 autorelease对象就可以在线程结束的时候释放,避免过多的延迟释放造成程序占用过多得内存.如果是一个寿命长的线程的话,应该创建更多的Autorelease Pool 来达到这个目的.例如线程中用到了 runloop 的时候,每一次的迭代都需要创建AutoreleasePool.
当创建线程的时候我们有两种选择,一种是线程执行一个很长的任务然后在任务结束的时候退出.另外一种是线程可以进入一个循环,然后处理动态到达的任务,这个时候就需要我们开启线程的 RunLoop,么一个线程都有一个 NSRunLoop,主线程是默认开启的,其他线程需要手动开启.
#import "AppDelegate.h"@interface AppDelegate ()@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self creatThread]; return YES;}-(void)creatThread{ NSThread *thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(threadMethod) object:nil]; [thread1 start];//完成线程的创建}// thread1的线程入口- (void)threadMethod { // 完善线程入口 // 1.给线程添加autoreleasePool @autoreleasepool { //添加一个 timer 模仿系统可以接收的事件 每过一秒唤醒 runloop 一次 NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerAction) userInfo:nil repeats:YES]; // 2. 开启线程的runloop [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; [[NSRunLoop currentRunLoop] run]; }}- (void)timerAction { NSLog(@"+++++++");}@end
2.线程的终止
终止线程最好不要用 POSIX接口直接杀死线程,这种方法会导致系统无法回收线程使用的资源,造成内存泄露,还有可能对程序的运行造成影响,终止线程最好的方式是能够让线程接收取消和退出消息,这样线程在收到消息的时候就有机会清理已持有的资源,避免内存泄露.如果需要在子线程运行的时候让子线程结束操作,子线程每次 RunLoop 迭代中检查相应的标志位来判断是否还需要继续执行,可以使用 theradDictionary 以及设置 InputSource 的方式通知这个子线程.这种方式的一种实现方式是使用 NSRunLoop 的InputSource来接收消息,每一次的 NSRunLoop 循环都检查退出条件是否为 YES,如果为 YES退出循环回收资源,如果为 NO ,则进入下一次 NSRunLoop 循环.
@interface AppDelegate (){ NSInteger _count;}@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //创建一个线程 NSThread *thread=[[NSThread alloc]initWithTarget:self selector:@selector(threadAction) object:nil]; //启动一个线程 启动了才创建出来 [thread start]; return YES;}- (void)threadAction{ @autoreleasepool { //获取当前的 runloop NSRunLoop *runLoop=[NSRunLoop currentRunLoop]; //获取当前runloop 的 threadDictionary 这个字典是只读的不能设置字典但是可以设置字典中的属性 可以通过设置这里面的属性来传递消息 NSMutableDictionary *threadDic=[[NSThread currentThread]threadDictionary]; [threadDic setValue:[NSNumber numberWithBool:NO] forKey:@"isExit"]; //模拟事件源 唤醒 runLoop NSTimer *timer=[NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES]; //添加事件源 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; //判断条件是否停止RunLoop while (![[threadDic objectForKey:@"isExit"] boolValue]) { //这个方法相当于走了一次循环 [runLoop runUntilDate:[NSDate date]]; } }}- (void)timerAction:(NSTimer *)timer{ _count ++; //执行几次结束线程 if (_count==10) { [[NSThread currentThread].threadDictionary setValue:[NSNumber numberWithBool:YES] forKey:@"isExit"]; } NSLog(@"%ld",_count);}@end
小结:
NSThread作为多线程编程的重要工具类,我们应该理解其中的方式,但是 NSThread 的缺点很明显,我们需要手动实现线程逻辑的管理,自己管理线程的生命周期,线程的同步,线程同步对数据的枷锁会有一定的系统开销.当然它的优点是轻量级 直观的控制线程对象.
二,NSOperationQueue
1.简介
使用 NSOperationQueue 方式进行多线程编程,不能像 NSThread 一样直接创建线程,也不需要管理, 但是可以间接的干预线程,这也是该方式的有点.NSOperationQueue 同时引入了 Queue 的概念,了解 NSOperationQueue 使用,首先要了解 NSOperation 和 NSOperationQueue 的关系.
2.NSOperationQueue 的使用
NSOperation类是一个抽象类,用来封装单任务的代码和数据.所以,我们不能直接使用该类,而是使用系统定义的子类来完成实际的任务.iOS 提供了两种默认方式 NSInvocationOperation 和 NSBlockOperation,当然也可以自定义 NSOperation.
需要注意的是,使用 NSOperation 的子类对象只能执行一次任务,而且不能再次执行它,可以将它添加到一个操作队列中执行操作,这个操作队列我们可以使用 NSOperationQueue 来实现.
配合使用 NSOperation和 NSOperationQueue 实现多线程编程.
3.创建 NSOperation 的创建方式
一种是用定义好的两个子类:NSInvocationOperation 和 NSBlockOperation
另一种是继承 NSOperation 创建子类:NSOperation 也是设计用来扩展的,只需继承重写 NSOperation 的一个方法 main.然后把 NSOperation子类的对象放入 NSOperationQueue 队列中,该队列就会启动并开始处理它
需要注意的是,NSOperation 默认是不执行的,需要调用 start 方法
注意:NSOperation 添加到 queue 之后,绝对不要修改 NSOperation 对象的状态.因为 NSOperation 对象可能会在任何时候运行,因此改变 NSOperation 对象的依赖或者数据会产生一些影响. 只能查看NSOperation 对象的状态,比如是否在运行,等待运行或已经完成等
4.NSOperation 的运行顺序
对于添加到 queue 中的 oerations,他们的运行顺序取决于两点
首先看看 NSOperation是否已经准备好:是否准备好由对象的依赖关系确定
然后再根据所有 NSOperation 的相对优先级来确定.
NSOperation 添加依赖关系
依赖关系会影响到 NSOperation 对象在 queue 中的执行顺序,当某个 NSIperation 对象依赖于其他 NSOperation 对象的完成时就可以通过 addDependency 方法添加一个或者多个依赖对象
[operation1 addDependency:operation2];
只有所有依赖的对象都已经完成操作,当前 NSOperation 对象才会开始执行操作.另外,通过 removeDependency方法来删除依赖对象
[operation1 removeDependency:operation2];
添加依赖成功的前提,必须将 Operation 添加到 Queue 中,但是要在将 oeration 添加到 Queue 中之前添加依赖.依赖关系不局限于相同 queue 中的 NSOperation 对象.NSOperation 对象会管理自己的依赖,因此完全可以在不同的 Queue 之间的 NSOperation 对象创建依赖关系
修改 Operations 的优先级
优先等级则是 operation 对象本身的一个属性.默认所有的 operation 都拥有”普通”优先级,不过可以通过 setQueuePriority:方法来提升或降低 operation 对象的优先级.优先级只能应用于相同 queue 中的 operations.
注意:优先级不能替代依赖关系,优先级只是对已经准备好的 operations 确定执行顺序.先满足依赖关系,然后根据优先级从所有准备好的操作中选择优先级最高的那个执行
5.NSOperationQueue 的相关操作
设置队列的最大并发操作数量
队列的最大并发操作数量,意思是队列中最多同时运行几条线程
虽然 NSOperationQueue 类设计用于并发执行 Operations,你也可以强制单个 queue 一次只能执行一个 Operation.色图MaxConcurrentOperationCount: 方法可以设置 queue 的最大并发操作数量.设置为1表示 queue 每次只能执行一个操作,所有 Operation 都会串行.但并不代表只有一个线程,operation 执行的顺序仍然依赖于其他因素,比如 Operation 是否准备好和 Operation 的优先级等.因此串行化的 operationQueue 并不等同于 GCD中的串行 dispatchQueue
取消 Operation
一旦添加到 queue 中 queue 就拥有了这个 Operation 对象并且不能被删除,唯一能做到的就是取消,可以调用 Operation 对象的 cancel 方法取消单个操作,也可以嗲用 operationQueue 的 cancelAllOperations 的方法取消当前队列中的所有操作
Operations 中的同步执行
操作放置到队列中,默认是异步执行,为了最佳的性能,你应该设计你的应用尽可能的异步操作,让应用在 Operation 正在执行时可以去处理其他事情.如果需要在当前线程中处理 operation 完成后的结果,可以使用 NSOperation 的 waitUntilFinished 方法阻塞当前线程.阻塞线程不是一个好的方式 造成”卡”的现象
除了等待单个 Operation 完成,你爷可以同时等待一个 queue 中的所有操作,使用 NSOperationQueue 的 waitUntilAllOperationsAreFinished 方法.
注意:在等待一个 queue 时,应用的其他线程仍然可以向 queue 中添加 Operation ,一次可能会加成线程的等待时间.
暂停和继续 queue
如果你想临时停止Operations 的执行,可以使用 queue 的 setSuspended:的方法暂停 queue.不过暂停一个 queue 不会导致正在执行的 operations 在停止,只是简单的组织调度新的 Operation 执行.你可以在相应用户请求时,暂停一个 queue 来暂停等待中的任务.稍后根据用户的请求,可以再次调用 setSuspended:方法继续 queue 中的 operation 的执行
#import "AppDelegate.h"#import "ZQOperation.h"@interface AppDelegate ()@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { //NSOperation //创建任务 NSBlockOperation *op1=[NSBlockOperation blockOperationWithBlock:^{ NSLog(@"222%@",[NSThread currentThread]); }]; ZQOperation *op2=[[ZQOperation alloc]init]; ZQOperation *op3=[[ZQOperation alloc]initWithBlock:^{ NSLog(@"333%@",[NSThread currentThread]); }]; //添加任务到队列 NSOperationQueue *queue=[[NSOperationQueue alloc]init]; //隐式添加 [queue addOperationWithBlock:^{ [queue cancelAllOperations];//关闭队列中所有的任务 NSLog(@"444%@",[NSThread currentThread]); }]; //设置最大并发数 (迅雷同时下载任务) [queue setMaxConcurrentOperationCount:2]; //通过阻塞线程实现同步 在这里是阻塞当前线程 [op1 waitUntilFinished]; //添加多个任务到队列, bool 表示当前线程的其他任务是否要等待任务的执行完成 yes 会导致主线程的卡死 [queue addOperations:@[op1,op2,op3] waitUntilFinished:NO]; //取消任务,已经运行的任务无法停止 [op3 cancel];//关闭任务 //添加依赖 //添加依赖在任务到队列之前,但是,添加依赖成功的前提是任务添加到队列中 [op1 addDependency:op3]; [op3 addDependency:op2]; //不能循环依赖 //设置优先级 [op3 setQualityOfService:NSQualityOfServiceUserInteractive]; return YES;}@end
继承 NSOperation
#import "ZQOperation.h"@implementation ZQOperation-(id)initWithBlock:(myBlock)block{ self = [super init]; if (self) { _block=block; } return self;}-(void)main{ if (_block) { _block(); } NSLog(@"111%@",[NSThread currentThread]);}@end
三.GCD
1.GCD简介
GCD(Grand Central Dispatch)调度中心,使用 GCD不需要管理线程,线程管理完全交给 GCD.
GCD提供了很多超越传统多线程编程的优势:
易用:可以控制等待任务结束,监视文件描述符,周期执行代码以及工作挂起等任务.基于 block 的血统导致他能极为简单的在不同代码作用域之间传递上下文
效率:
性能:GCD自动根据系统负载来增加线程数量,减少了上下文的切换.
2.dispatch queue
GCD中的一个重要组成部分就是队列,我们把给中任务提交到队列,队列根据它本身的类型以及当前系统的状态, 添加到不同的线程中执行任务.线程的创建和管理都由 GCD本身完成,不需要我我们参与.系统提供了很多定义好的队列:只管理主线程的串行 main_queue,全局并行的 globle_queue.同时我们亦可以自定义自己的队列 queue.
queue 的种类
并行 queue:可以多个线程并行,任务可以同时执行
串行 queue:所有线程串行,或者只有一个线程,任务一次执行 这里和 operation 设置队列的最大并发操作数量为1的时候很相似
创建自定义的 queue
queue 的类型为 dispatch_queue_t
创建函数是 dispatch_queue_creat()
dispatch_queue_creat(const char *label,dispatch_queue_attr_t attr)
第一个参数表示 queue 的名字,注意 char指针,并非 NSString
第二个参数表示 queue 的类型
DISPATCH_QUEUE_SERIAL表示串行
DISPATCH_QUEUE_CONCURRENT表示并行
获得预定义的 globle queue
除了自己定义的 queue 以外,我们可以使用系统预定义的 queue,包括两种.
获取全局queue,该队列的种类是并行 queue,也就是说我们可以直接提交给这个 queue,任务会在非主线程的其他线程执行.
dispatch_get_global_queue(long identifier, unsigned long flags); queue , :#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
获取主线程的 queue
mainqueue 的中了属于串行队列,提交给该队列的任务,会加入到主线程中.提交给 mainqueue 的任务可能不会立马执行,而是在主线程的 RunLoop 检测到有 dispatch 提交过来的任务时才会执行.
dispatch_get_main_queue();
3.提交任务
提价任务分为两种方式,异步和同步.同步提交任务,会等待任务完成.异步不需要等待任务完成.
同步提交任务
同步提交函数为 dispatch_sync(),有两个参数
第一个表示提交到的 queue
第二个参数表示任务详情
这里用 block 的方式描述一个任务,原因很简单,block 是纯 C实现的,而平时使用的 Invocation 或者 target+selector 方式都是面相对象的.
注意:同步提交任务后,先执行完 block,然后 dispatch_sync()返回, 当使用 main_queue这时候如果同步提交的任务过长会导致主线程卡死.
异步提交任务
异步提交函数为dispatch_async()两个参数
第一个表示提交到的 queue
第二个表示任务详情
异步提交任务后,dispatch_async()函数直接返回,无需等待 block 执行结束.不会导致主线程卡死.
同时提交多次任务
同时提交多个任务给 queue 函数很简单:dispatch_apply(size_t iterations, dispatch_queue_t queue,void (^block)(size_t));
三个参数分别是,任务数,目标 queue,任务描述
这里描述的任务的 block 会重复多次调用,每次我们会给我们一个参数,表示任务次序.多个任务的执行顺序取决于添加队列的 queue 的形式, 如果目标 queue 是串行的,那么任务会依次执行,如果 queue 是并行的,那么任务会并发执行,打印的顺序就会被打乱.
这里我们可以用这个特性做一个 baseModel 可以实现数据的异步加载 提高加载速度
死锁
同步提交在某种情况下会造成死锁,
dispatch_queue_t mainQ = dispatch_get_main_queue(); dispatch_sync(mainQ, ^{NSLog(@"----"); });NSLog(@"OK");
dispatch_queue_t q_1 = dispatch_queue_create("task3.queue. 1",DISPATCH_QUEUE_SERIAL);dispatch_async(q_1, ^{NSLog(@"current is in q_1");// q_1 blcok q_1 q_1 dispatch_syncblock block dispatch_sync dispatch_sync(q_1, ^{ NSLog(@"this is sync ");});NSLog(@"this is sync ????");});
综合上面的两个代码,我们可以看出 当我在一个串行队列中添加同步任务的时候 会出现 block 中的代码不执行完这个队列就不会结束 而这个队列不结束 block 中的代码就不会执行 出现死锁的现象.为了避免出现死锁,我们要避免在串行队列中同步提交任务给本身的队列.
4.queue 的暂停和继续
我们可以使用 dispatch_suspend 函数暂停一个 queue 来阻止它执行尚未 block 对象,使用 dispatch_resume 函数继续 dispatch queue.挂起和继续是异步的,而且只在执行 block 之前(比如在执行一个新的 block 之前或之后)生效.国企一个 queue 不会导致正在执行的 block 停止.特别强调,需要我们保证挂起队列和重启队列的函数成对调用,也就是使用了dispatch_suspend必须使用dispatch_resume.
5.dispatchGroup
在 NSOperation 中添加依赖在这里我们使用 dispatch group.
设置任务执行顺序
#import "AppDelegate.h"@interface AppDelegate ()@end@implementation AppDelegate- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { [self creatGroup]; return YES;}- (void)creatGroup { // 创建group及任务队列 dispatch_group_t group = dispatch_group_create(); dispatch_queue_t globleQ = dispatch_get_global_queue(0, 0); // 提交任务到group dispatch_group_async(group, globleQ, ^{ usleep(200000); NSLog(@"1111"); }); dispatch_group_async(group, globleQ, ^{ usleep(20000); NSLog(@"2222"); }); dispatch_group_async(group, globleQ, ^{ usleep(10000000); NSLog(@"3333"); }); // 提交最终的任务,并没有添加到group中,并不会影响主线程的执行 这个最终任务必须等到 group 中的任务全部执行完毕才执行 暂停 group 中的任务也会延迟这个任务 dispatch_queue_t queue_1 = dispatch_queue_create("queue_1",DISPATCH_QUEUE_SERIAL); dispatch_group_notify(group, queue_1, ^{ NSLog(@"4444"); }); // 一直等待group的任务完成之后再去执行当前线程其他方法// dispatch_group_wait(group, DISPATCH_TIME_FOREVER); // 设置超时时间 这个方法会等待 group 中的任务执行 如果没有执行完毕但是我自己填写的等待事件到了 我就不会等它了 执行后面的方法先打印5555 dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(8 * NSEC_PER_SEC)); dispatch_group_wait(group, time); NSLog(@"5555");}@end
当前状态打印的内容
2016-01-18 09:17:37.228 02 GCD[6741:617044] 2222
2016-01-18 09:17:37.408 02 GCD[6741:617047] 1111
2016-01-18 09:17:45.210 02 GCD[6741:616931] 5555
2016-01-18 09:17:47.207 02 GCD[6741:617043] 3333
2016-01-18 09:17:47.207 02 GCD[6741:617047] 4444
注意:提交任务到 group 是没有同步提交的,只有异步提交 这就实现了依赖
小结:NSThread 多做理解 NSOperation 使用起来比较好理解 但是 GCD对于其内部结构是最好的官方推荐使用 只是用的都是 C来写的不好看
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程 笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程(笔记)
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- 多线程笔记
- Android中常见的坑有哪些?
- 刷新验证码
- MySQL 内置数学函数
- mysql处理数据库中的部分数据
- UIAutomator2.0 简介
- 多线程笔记
- java中List的用法
- 如何使用Spring开发和监控线程池服务
- angularjs----mark
- Java之学习笔记(30)------------枚举
- listview与adapter用法
- Android性能优化之如何避免Overdraw
- 指定初始化器designated initializer的意义以及[super init...]意义的个人心得
- android Blur模糊效果