美文网首页
多线程全知道

多线程全知道

作者: 冲上云霄90 | 来源:发表于2016-07-04 09:32 被阅读61次

    iOS中三种多线程编程技术分别是:

    • NSThread
    • Cocoa NSOperation
    • GCD (全称: Grand Central Dispatch)

    这三种编程方式从上到下, 抽象度层次从低到高, 抽象度越高的使用越简单, 也是Apple最推荐使用的.

    各自优缺点:

    • NSThread:

    优点: NSThread 比其他两个轻量级
    缺点: 需要自己管理线程的生命周期, 线程同步. 而线程同步对数据的枷锁会有一定的系统开销.

    • NSOperation

    优点: 不需要关心线程管理, 数据同步的事情, 可以把经理放在自己需要执行的操作上. Cocoa operation 相关的类是 NSOperation, NSOperationQueue.
    NSOperation 是个抽象类, 使用它必须用它的子类, 可以实现它挥着使用它自定义的两个子类: NSInvocationOperation和 NSBlockOperation.
    创建NSOperation子类的对象, 把对象添加到NSOPerationQueue对队列里执行.

    • GCD

    Grand Central Dispatch(GCD)是Apple开发的一个多核编程的解决方法, 在iOS4.0 开始之后才能使用. GCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术, 现在的iOS系统升级 不用担心技术不能使用.

    NSThread的使用

    NSThread 有两种直接创建方式

    一. 实例方法 (先创建线程并要有个开始start指令,这里可设置线程的优先级等线程信息)

    - (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 
    

    二. 类方法 (直接创建线程并开始运行线程)

    + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument
    

    参数的意义:

    • selector :线程执行的方法,这个selector只能有一个参数,而且不能有返回值。
    • target :selector消息发送的对象
    • argument:传输给target的唯一参数,也可以是nil

    不显式创建线程的方法:

    用NSObject的类方法 performSelectorInBackground:withObject: 创建一个线程:

    [Obj performSelectorInBackground:@selector(doSomething) withObject:nil];
    

    线程间通讯

    线程下载完图片后怎么通知主线程更新界面呢?

    [self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES];
    

    performSelectorOnMainThread是NSObject的方法,除了可以更新主线程的数据外,还可以更新其他线程的比如:

    performSelector:onThread:withObject:waitUntilDone:
    

    线程同步

    如果没有线程同步的lock,卖票数可能是-1.加上lock之后线程同步保证了数据的正确性。
    线程的顺序执行

    他们都可以通过[ticketsCondition signal]; 发送信号的方式,在一个线程唤醒另外一个线程的等待。

    使用****NSCondition****,实现多线程的同步,即可实现生产者消费者问题。

    基本思路是,首先要创建公用的****NSCondition****实例。然后:
    消费者取得锁,取产品,如果没有,则wait,这时会释放锁,直到有线程唤醒它去消费产品;
    生产者制造产品,首先也是要取得锁,然后生产,再发signal,这样可唤醒wait的消费者。

    这里需要注意****wait****和****signal****的问题:
    1: 其实,wait函数内部悄悄的调用了unlock函数(猜测,有兴趣可自行分析),也就是说在调用wati函数后,这个NSCondition对象就处于了无锁的状态,这样其他线程就可以对此对象加锁并触发该NSCondition对象。当NSCondition被其他线程触发时,在wait函数内部得到此事件被触发的通知,然后对此事件重新调用lock函数(猜测),而在外部看起来好像接收事件的线程(调用wait的线程)从来没有放开NSCondition对象的所有权,wati线程直接由阻塞状态进入了触发状态一样。这里容易造成误解。
    2: wait函数并不是完全可信的。也就是说wait返回后,并不代表对应的事件一定被触发了,因此,为了保证线程之间的同步关系,使用NSCondtion时往往需要加入一个额外的变量来对非正常的wait返回进行规避。
    3: 关于多个wait时的调用顺序,测试发现与wait执行顺序有关。具体请查阅文档。

    其它同步:

    可使用指令 @synchonized 来简化 NSLock 的使用, 这样不必显示创建NSLock, 加锁并解锁相关代码.
    除外还有比如:循环锁 NSRecursiveLock, 条件锁 NSConditionLock, 分布式锁 NSDistributedLock等等

    Cocoa NSOperation 的使用

    使用 NSOperation 的方式有两种,
    一种是用定义好的两个子类:

    • NSInvocationOperation 和 NSBlockOperation.
    • 另一种是继承NSOperation

    NSOperation 和 java.lang.Runnable接口很相似. 和其一样, NSOperation 也是设计用来扩展的, 只需继承重写NSOperation的一个main, 相当于 java 中Runnable的 Run方法. 然后把NSOperation子类的对象放入 NSOperationQueue队列中, 该队列就会启动并开始处理它.

    默认情况下,NSOperation并不具备封装操作的能力,必须使用它的子类,使用NSOperation子类的方式有3种:

    1> 自定义子类继承NSOperation,实现内部相应的方法
    2> NSBlockOperation
    3>NSInvocationOperation

    先介绍如何用NSOperation封装一个操作,后面再结合NSOperationQueue来使用。
    1.****首先介绍自定义****NSOperation****:
    NSOperation是没法直接使用的,它只是提供了一个工作的基本逻辑,具体实现还是需要你通过定义自己的NSOperation子类来获得。

    #import <Foundation/Foundation.h>
    @protocol NSDefineOprationDelegate <NSObject>
    - (void) handleDelegate;
    @end
    @interface NSDefineOpration : NSOperation
    @property (nonatomic, assign) id <NSDefineOprationDelegate> delegate;
    - (id)initWithDelegate:(id<NSDefineOprationDelegate>) delegate;
    @end
    

    实现文件里:

    #import "NSDefineOpration.h"
    @implementation NSDefineOpration
    - (id)initWithDelegate:(id<NSDefineOprationDelegate>) delegate{
        if(self = [super init]){
            self.delegate = delegate;
        }
        return self;
    }
    - (void)main{
        @autoreleasepool {
            //do something
            sleep(15);
            NSLog(@"op1........handle......  on thread num :%@",[NSThread currentThread]);
            
            if([self.delegate respondsToSelector:@selector(handleDelegate)])
            {
                [self.delegate performSelector:@selector(handleDelegate) withObject:nil];
            }
        }
        
    }
    @end
    

    这里的sleep(15)主要用来做一些延时的操作,比如网络下载等。
    调用:

    - (void)oprationTest
    {
        NSDefineOpration *op1 = [[NSDefineOpration alloc] initWithDelegate:self];
        op1.completionBlock = ^(){
            NSLog(@"op1........OK !!");
        };
        [op1 start];
    }
    

    从执行结果可以看出,因为在实现的main函数里没有使用异步线程处理,导致直接阻塞了主线程1,所以使用这种方式一定注意main函数里操作时间过长导致主线程阻塞问题。耗时比较长的都放到其他线程里处理。

    2.****接下来介绍****NSBlockOperation
    第一种使用NSBlockOperation的方式 block类方法

        NSLog(@"block start");
        NSBlockOperation *bop2 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(15);
            NSLog(@"bop2.....handle..... on thread num%@",[NSThread currentThread]);
        }];
        [bop2 setCompletionBlock:^{
            NSLog(@"bop2........OK !!");
        }];
        [bop2 start];
    

    首先初始化了一个NSBlockOperation对象,它是用一个Block来封装需要执行的操作 调用了start方法,紧接着会马上执行Block中的内容
    这里还是在当前线程同步执行操作,并没有异步执行,阻塞主线程。

    第二种使用NSBlockOperation的方式: alloc 实例方法

    - (void)blockOprationTest
    {
        NSLog(@"block start");
        NSBlockOperation * op2 = [[NSBlockOperation alloc] init];
        [op2 addExecutionBlock:^{
            sleep(10);
            NSLog(@"op2.....handle..... on 10 hread num%@",[NSThread currentThread]);
        }];
        
        [op2 addExecutionBlock:^{
            sleep(6);
            NSLog(@"op2.....handle..... on 6 thread num%@",[NSThread currentThread]);
        }];
        
        [op2 addExecutionBlock:^{
            sleep(4);
            NSLog(@"op2.....handle..... on 4 thread num%@",[NSThread currentThread]);
        }];
        
        [op2 addExecutionBlock:^{
            sleep(8);
            NSLog(@"op2.....handle..... on 8 thread num%@",[NSThread currentThread]);
        }];
        
        [op2 addExecutionBlock:^{
            sleep(1);
            NSLog(@"op2.....handle..... on 1 thread num%@",[NSThread currentThread]);
        }];
        [op2 setCompletionBlock:^{
            NSLog(@"op2........OK !!");
        }];
        [op2 start];
        
        //bop2
        NSBlockOperation *bop2 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(15);
            NSLog(@"bop2.....handle..... on thread num%@",[NSThread currentThread]);
        }];
        [bop2 setCompletionBlock:^{
            NSLog(@"bop2........OK !!");
        }];
        [bop2 start];
    }
    

    分析下结果:
    首先看到了有1和2两个线程,线程2在56秒的时候开始执行6秒的操作,接下来执行4,1秒结束时间为07秒。线程1在56的时候开始执行10秒的操作,接下来执行8秒,结束时间为14秒。最后执行Bop2的15秒操作至29秒。时间看起来没有问题。为什么会启用2个线程而不是3个或者更多?

    3.****接下来介绍****NSInvocationOperation

    - (void)invocationOperation
    {
        NSInvocationOperation * op3 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOpDelegate) object:nil];
        [op3 setCompletionBlock:^{
            NSLog(@"op3........OK !!");
        }];
        [op3 start];
    }
    selector函数:
    - (void)handleInvoOpD
    {
        sleep(5);
        NSLog(@"op3.....handle.....  on thread num :%@",[NSThread currentThread]);
    }
    

    NSInvocationOperation比较简单,就是继承了NSOperation,区别就是它是基于一个对象和selector来创建操作,可以直接使用而不需继承来实现自己的操作处理。

    4.****最后介绍下****NSOperationQueue
    把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。
    eg:

    - (void)handleOpqueue
    {
        NSOperationQueue *qu = [[NSOperationQueue alloc] init];
        
        NSBlockOperation * bkOp1 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(10);
            NSLog(@"bkOp1.....handle.....on thread num%@",[NSThread currentThread]);
        }];
        [bkOp1 setCompletionBlock:^{
            NSLog(@"bkOp1........OK !!");
        }];
        
        
        NSBlockOperation * bkOp2 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(2);
            NSLog(@"bkOp2.....handle.....on thread num%@",[NSThread currentThread]);
        }];
        [bkOp2 setCompletionBlock:^{
            NSLog(@"bkOp2........OK !!");
        }];
        
        NSBlockOperation * bkOp3 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(1);
            NSLog(@"bkOp3.....handle.....on thread num%@",[NSThread currentThread]);
        }];
        [bkOp3 setCompletionBlock:^{
            NSLog(@"bkOp3........OK !!");
        }];
        
        NSBlockOperation * bkOp4 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(10);
            NSLog(@"bkOp4.....handle.....on thread num%@",[NSThread currentThread]);
        }];
        [bkOp4 setCompletionBlock:^{
            NSLog(@"bkOp4........OK !!");
        }];
        
        NSBlockOperation * bkOp5 = [NSBlockOperation blockOperationWithBlock:^{
            sleep(5);
            NSLog(@"bkOp5.....handle.....on thread num%@",[NSThread currentThread]);
        }];
        [bkOp5 setQueuePriority:NSOperationQueuePriorityHigh];
        [bkOp5 setCompletionBlock:^{
            NSLog(@"bkOp5........OK !!");
        }];
        
        NSInvocationOperation *invoOp6 = [[NSInvocationOperation alloc] initWithTarget:(id)self selector:@selector(handleInvoOp) object:nil];
        [invoOp6 setCompletionBlock:^{
            NSLog(@"invoOp6........OK !!");
        }];
        [invoOp6 setQueuePriority:NSOperationQueuePriorityHigh];
        
        [qu setMaxConcurrentOperationCount:2];
        [qu addOperation:bkOp3];
        [qu addOperation:bkOp2];
        [qu addOperation:bkOp1];
        [qu addOperation:bkOp4];
        [qu addOperation:bkOp5];
        [qu addOperation:invoOp6];
    }
    

    在设置了bkop5以及invOp6的优先级为高时,他们会优先执行,当然这个优先时相对,是相对正在排队的,不包括已经正在执行的。

    总结:NSOperation、NSBlockOperation、NSInvocationOperation、NSOperationQueue都比较简单,NSOperation、NSBlockOperation、NSInvocationOperation单个都是表示一种操作,而NSOperationQueue是一个可以包含多个NSOperation的队列,可以自己在多个线程处理,只要加入队列之后,我们就不用去操作,直到Callback或者完成。

    ** 3. GCD****的介绍和使用**
    介绍: Grand Central Dispatch 简称(GCD)是苹果公司开发的技术, 以优化的应用程序支持多核心处理器和其他的对称处理系统的系统. 这简历在任务并行执行的线程池模式的基础上的. 它首次发布在Mac OS X 10.6, iOS 4及以上可用.

    设计: GCD的工作原理是: 让程序平行排列的特定任务, 根据可用的处理资源, 安排他们在任何可用的处理器核心上执行任务.

    一个任务可以是一个函数(function)或者是一个block. GCD的底层依然是用线程实现, 不过这样可以让程序员不用关注实现的细节.

    GCD中的FIFO队列称为Dispatch Queue, 它可以保证先进来的任务先得到执行.

    Dispatch Queue分下面三种:
    Serial
    又称为private dispatch queues, 同时只执行一个任务. serial queue 通常用于同步访问特定的资源或数据. 当你创建多个 Serial Queue 时, 虽然他们各自是同步执行的, 但 Serial Queue 与 Serial queue之间是并发执行的.

    Concurrent
    又称为global dispatch queue, 可以并发执行多个任务, 但执行完成的顺序是随机的.

    Main dispatch queue
    它是全局可用的serial queue, 它是在应用程序主线程上执行任务的.

    常用的方法 dispatch_async

    为了避免界面在处理耗时的操作时卡死, 比如读取网络数据, IO, 数据库读写等, 我们会在另外一个线程中处理这些操作, 然后通知主线程更新界面.

    用GCD实现这个流程的操作比前面介绍的 NSThread NSOperation 的方法都要简单. 代码框结构如下:

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{   
        NSURL * url = [NSURL URLWithString:@"http://image.tianjimedia.com/uploadImages/2012/233/38/H439I0N71ARI.jpg"];   
        NSData * data = [[NSData alloc]initWithContentsOfURL:url];   
        UIImage *image = [[UIImage alloc]initWithData:data];   
        if (data != nil) {   
            dispatch_async(dispatch_get_main_queue(), ^{   
                self.imageView.image = image;   
             });   
        }   
    });
    

    相比前两种, GCD会自动根据任务在多喝处理器上分配资源, 优化程序.

    系统给每一个应用程序提供了三个 concurrent dispatch queues. 这三个并发调度队列是全局的, 他们只是优先级的不同, 因为是全局的, 我们不需要去创建,. 我们只需要通过使用函数dispath_get_global_queue去得到队列, 如下:

    dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    

    这里也用到了系统默认就有一个串行队列main_queue:
    dispatch_queue_t mainQ = dispatch_get_main_queue();

    虽然dispatch queue 是引用计数对象, 但是以上两个都是全局的队列, 不用retain 或 release.

    dispatch_group_async的使用

    dispatch可以监听一组任务是否完成, 完成后得到通知执行其他的操作. 这个方法很有用, 比如你执行三个下载任务, 当三个任务都下载完成后你才通知界面说完成的了, 举例:

    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, ^{   
        [NSThread sleepForTimeInterval:1];   
        NSLog(@"group1");   
    });   
    dispatch_group_async(group, queue, ^{   
        [NSThread sleepForTimeInterval:2];   
        NSLog(@"group2");   
    });   
    dispatch_group_async(group, queue, ^{   
        [NSThread sleepForTimeInterval:3];   
        NSLog(@"group3");   
    });   
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{   
        NSLog(@"updateUI”);   
    });   
    dispatch_release(group);
    

    dispatch_group_async是异步的方法,运行后可以看到打印结果:
    每隔一秒打印一个,当第三个任务执行后,upadteUI被打印。

    dispatch_barrier_async的使用

    dispatch_barrier_async是在前面的任务执行结束后他才执行, 而且它后面的任务等它执行完成之后才会执行
    举例:

    dispatch_queue_t queue = dispatch_queue_create(“haha.com”, DISPATCH_QUEUE_CONCURRENT);   
    dispatch_async(queue, ^{   
        [NSThread sleepForTimeInterval:2];   
        NSLog(@"dispatch_async1");   
    });   
    dispatch_async(queue, ^{   
        [NSThread sleepForTimeInterval:4];   
        NSLog(@"dispatch_async2");   
    });   
    // 先执行完前面的在执行后面的
    dispatch_barrier_async(queue, ^{   
        NSLog(@"dispatch_barrier_async");   
        [NSThread sleepForTimeInterval:4];   
    
    });   
    dispatch_async(queue, ^{   
        [NSThread sleepForTimeInterval:1];   
        NSLog(@"dispatch_async3");   
    });
    

    dispatch_apply
    执行某个代码片段N次.

    dispatch_apply(5, globalQ, ^(size_t index)) {
     // 执行5次
    }
    

    相关文章

      网友评论

          本文标题:多线程全知道

          本文链接:https://www.haomeiwen.com/subject/blmcjttx.html