多线程笔记

来源:互联网 发布:淘宝未发货算退款率吗 编辑:程序博客网 时间: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来写的不好看

0 0