多线程实现之GCD、NSThred、NSOperation

在iOS中实现多线程技术的方法:pthread、NSThread、GCD、NSOperation

多线程的实质就是开辟新的线程、添加队列、在队列中添加同步任务或者异步任务

一、pthread是一套通用的C语言的多线程API,适用于Unix,Linux,Windows等系统,可跨平台,使用难度大,几乎不用

二、NSThread:

是OC的线程对象,一个NSThread对象就是一条线程;

    1、创建线程的方式:

           1>  先创建在启动线程 

    //一个线程对应一个runloop,创建一个线程会自动开辟一个runloop
    NSThread * runloopTherd = [[NSThread alloc]initWithTarget:self selector:@selector(runLoop) object:nil];
    //事情做完后才会死
    [runloopTherd start];

         2>  直接创建并启动线程

    //直接创建并启动线程
    [NSThread detachNewThreadSelector:@selector(runLoop) toTarget:self withObject:nil];

            3>  隐式创建 直接创建并启动

//直接创建并开启线程
[NSThread performSelectorInBackground:@selector(runThread) withObject:@"my"];

   2、线程之间的通信:当线程A传递数据给线程B,在线程B中完成特定的任务之后,再转到A继续

例如:图片下载并显示

当触摸屏幕的时候
    //获取图片的url
    NSURL * url  = [NSURL URLWithString:@""];
    //由于下载图片是一个耗时的操作,需要开辟一条线程,object用来传递数据
    NSThread * threadImage = [[NSThread alloc]initWithTarget:self selector:@selector(downLoadImage:) object:url];
    [threadImage start];

-(void)downLoadImage:(NSURL *)urlstr
{
    //下载图片
    NSData * imageData = [NSData dataWithContentsOfURL:urlstr];
    //生成图片
    UIImage * downImage = [UIImage imageWithData:imageData];
    //返回主线程赋值图片
    [self.imageView performSelectorOnMainThread:@selector(setImage:) withObject:downImage waitUntilDone:YES];
}

三、GCD实现多线程

GCD:Grand  Central Dispatc,强大的中央调度器,是苹果公司为多核的并行原酸提出的解决方案,会自动根据CPU内核来开启线程执行任务,GCD会自动管理线程的生命周期,创建线程、任务调度、线程销毁,不需要我们自己手动管理内存

1、基本术语

任务:block 需要执行的操作,下载还是播放等

队列:Queue 用来放任务的,任务取出的时候应该是先进先出,因此放在队列中,包括并发队列和串行队列

同步:当前线程中可以立即执行任务,不具备开启线程的能力

异步:当前线程结束时执行任务,具备开启线程的能力

并发队列:可以让多个任务同时进行,自动开启多个线程同时执行

串行队列:顺序的执行,五张图片一张一张的下载

2、创建队列----串行、并行、主队列、全局队列

  *** 主队列就在主线程中执行,并且主队列不具备开线程的能力

    /*函数 dispatch_queue_create 两个参数
     const char * label  队列名称
     dispatch_queue_attr_t  attr   队列类型
     DISPATCH_QUEUE_SERIAL   串行
     DISPATCH_QUEUE_CONCURRENT   并发
     */
    //创建串行队列
    dispatch_queue_t serial =  dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);
    //创建并发队列
    dispatch_queue_t concurrent =  dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
    //全局队列:全局队列是并发队列
    /*
     参数1 : long identifier  队列的优先级
     
     #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   后台
     
     参数2 : unsigned long flags  队列参数,一般写0
     */
    dispatch_queue_t global = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
    
    //获取主队列。主队列中的任务都会在主线程中执行
    dispatch_queue_t mainqueue = dispatch_get_main_queue();
    

3、同步、异步函数

//========================      同步      ============================
    /*
     函数 dispatch_sync ()
        参数:dispatch_queue_t  _Nonnull queue 哪个队列
        参数:<#^(void)block#>    任务
     */
    
    // 同步串行队列,马上执行,在当前线程
    dispatch_sync(serial, ^{
        NSLog(@"~~~%@~~~", [NSThread currentThread]);
    });
    // 同步并行队列,马上执行,在当前线程
    dispatch_sync(concurrent, ^{
        NSLog(@"~~~%@~~~", [NSThread currentThread]);
    });
//========================      异步      ============================
    /*
     函数 dispatch_aasync ()
     参数:dispatch_queue_t  _Nonnull queue 哪个队列
     参数:<#^(void)block#>    任务
     */
    
    //异步函数串行队列,开辟线程,多个任务按顺序执行
    dispatch_async(serial, ^{
        
        dispatch_async(serial, ^{
            NSLog(@"~~~%@~~~", [NSThread currentThread]);
        });
        dispatch_async(serial, ^{
            NSLog(@"~~~%@~~~", [NSThread currentThread]);
        });
        dispatch_async(serial, ^{
            NSLog(@"~~~%@~~~", [NSThread currentThread]);
        });
    });
    
    //异步函数并行队列,开辟线程,多个任务一起执行
    dispatch_async(concurrent, ^{
        dispatch_async(serial, ^{
            NSLog(@"~~~%@~~~", [NSThread currentThread]);
        });
        dispatch_async(serial, ^{
            NSLog(@"~~~%@~~~", [NSThread currentThread]);
        });
        dispatch_async(serial, ^{
            NSLog(@"~~~%@~~~", [NSThread currentThread]);
        });
    });

使用同步函数添加任务A到串行队列,说明要在当前串行队列立即执行A,任务A执行完后,才会执行任务A后面的代码。也就是说任务A必须要等到当前串行队列执行完成任务B后才能执行,因此必须先执行A中立即添加的任务,又要必须等到任务B执行完才能执行下一个任务,会死循环,卡死。谁也无法执行

4、GCD--线程之间的通信     下载图片的例子

    NSURL * disUrl = [NSURL URLWithString:@""];
    //异步开一个线程下载图片
    dispatch_async(dispatch_queue_create("image", DISPATCH_QUEUE_PRIORITY_DEFAULT), ^{
        
        NSData * disData = [NSData dataWithContentsOfURL:disUrl];
        UIImage * disImage = [UIImage imageWithData:disData];
        //返回主线程使用图片
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = disImage;
        });
        
    });

5、GCD其他常用函数

1>  dispatch_barrier  栅栏、障碍、界限

      在barrier之前的先执行,然后执行barrier,再执行barrier后面的,而barrier的队列不能是全局的并发队列    

      应用:读写锁

      例子:假如我们在平常编码中,要保证某个属性线程安全的读写,一般加锁方式:这就是atomic的加锁方式,这种方式不一定就是安全的,在访问属性时,如果在一个县城上多次调用getter方法,每次得到的值不一定相同,在两次读操作之间也可能会写入新的shu'xing'zh

- (void)setAge:(NSString *)age
{
    @synchronized(self){
        _age = [age copy];
    }
}
-(NSString *)age
{
    @synchronized(self){
        return _age;
    }
}

所以我们就用到了最优写法,加上栅栏,也就是barrier

- (void)setAge:(NSString *)age
{
    dispatch_barrier_async(queue, ^{
        _age = [age copy];
    });
}
-(NSString *)age
{
    __block NSString * testAge;
    dispatch_sync(queue, ^{
         testAge = _age;
    });
    return testAge;
}

这段代码中加上了dispatch_barrier_async函数,也就是说在读操作中要等之前加的写操作完成后才能执行。 

2 > dispatch_after 延迟执行

-(void)after{
    //方法1   延迟两秒执行
    [self performSelector:@selector(run) withObject:@"参数" afterDelay:2.0];
    //方法2
    /*
     dispatch_time(dispatch_time_t when, int64_t delta);
     
     #define DISPATCH_TIME_NOW (0ull)
     #define DISPATCH_TIME_FOREVER (~0ull)
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    });
    //方法3
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}

3 > 单例模式,在整个应用程序中共享一份资源,只需要初始化一次

+(instancetype)sharePerson
{
    static Person * person = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        person = [[Person alloc]init];
    });
    return person;
}
+(instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        person = [Person allocWithZone:zone];
    });
    return person;
}
-(id)copy
{
    return person;
}

  面试题:口述单例创建的过程

  创建一个用static修饰的全局变量,并置为nil,使用dispatch_onece函数检查是否为nil,如果是nil就创建一个并返回全局实例,需要实现allocwithzone方法,并且为了防止由于多次访问而得到新的实例,需要重写copy方法,返回本身。

4 > dispatch_group 队列组

队列组是把相关的任务添加到一个组中进行,通过监听组内所有任务的情况作出相应处理;比如多张图片下载,并且合成新图片

-(void)dispatchGroup
{
    //创建队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建组
    dispatch_group_t group = dispatch_group_create();
    
    //用组队列下载图片1
    dispatch_group_async(group, queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@""]];
        self.imageOne = [UIImage imageWithData:imageData];
    });
    //用组队列下载图片2
    dispatch_group_async(group, queue, ^{
        NSData * imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:@""]];
        self.imageTwo = [UIImage imageWithData:imageData];
    });
    //将图1和图2合并成一张新图片
    dispatch_group_notify(group, queue, ^{
        CGFloat imageH = self.imageView.bounds.size.height;
        CGFloat imageW = self.imageView.bounds.size.width;
        //开启图形上下文
        UIGraphicsBeginImageContext(self.imageView.bounds.size);
        //画图
        [self.imageOne drawInRect:CGRectMake(0,0,imageW/2,imageH)];
        [self.imageTwo drawInRect:CGRectMake(imageW/2, 0, imageW/2, imageH)];
        //将图片取出
        UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
        //关闭图形上下文
        UIGraphicsEndImageContext();
        
        //回到主线程加载图片
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = newImage;
        });
    });
}

5 > 定时器,GCD定时器不受Mode的影响,因此要比NSTimer准确

//
//  GCDTimer.m
//  testGCD
//
//  Created by 李宁 on 2018/8/28.
//  Copyright © 2018年 李坏. All rights reserved.
//

#import "GCDTimer.h"
@interface GCDTimer()
@property (nonatomic,strong)dispatch_source_t  timer;
@end
@implementation GCDTimer

-(void)myTimer
{
    static int count = 0;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0* NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

    });
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    //创建一个定时器
    /*
     dispatch_source_create
     参数一:dispatch_source_type_t   定时器的类型
     参数二:uintptr_t handle         句柄
     参数三:unsigned long mask       一般写0
     参数四:dispatch_queue_t        对列,dispatch_source_t是OC的对象
     */
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    //回调函数的时间间隔,为了严谨,用int64,相乘以后就变了
    int64_t intervarl = (int64_t)(2.0* NSEC_PER_SEC);
    
    //设置开始时间  从现在开始3s后开始
    /*
    函数 dispatch_time
    参数一:dispatch_time_t when
     #define DISPATCH_TIME_NOW (0ull)   现在开始
     #define DISPATCH_TIME_FOREVER (~0ull)    啥时候开始都可以
    参数二:int64_t delta
     */
    dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0* NSEC_PER_SEC));
    //设置定时器的各种属性
    /*
     函数:dispatch_source_set_timer
     参数一:dispatch_source_t  _Nonnull source,  timer
     参数二:dispatch_time_t start,               开始时间
     参数三:uint64_t interval,                   时间间隔
     参数四:uint64_t leeway                      不需要传
     */
    dispatch_source_set_timer(self.timer, start, intervarl, 0);
    
    //设置回调,即每次的事件间隔需要做什么
    dispatch_source_set_event_handler(self.timer, ^{
        
        NSLog(@"I am a  timer");
        //如果希望三次之后就停止
        count ++;
        if(count > 3){
            dispatch_cancel(self.timer);
            self.timer = nil;
        }
    });
    
    //恢复定时器
    dispatch_resume(self.timer);
}
@end

四、NSOperation

        NSOperation是个抽象类,并不具备封装操作的能力,他依赖于两个子类

        1、NSInvocationOperation

        2、NSBlockOperation

        3、自定义子类继承自NSOperation,实现内部的相应的方法

  2、使用NSOperation实现多线程的步骤

         1 >  创建NSOperation对象

         2 >  创建NSOperationQueue队列

         3 >  将NSOperation对象添加到NSOperationQueue中

    NSInvocationOperation * option = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(my) object:nil];
    //调用start并不会开辟新的线程而是在当前线程中同步执行,只有将operation对象加到队列中才会异步
    [option start];
    
    NSBlockOperation * blockOp = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"--%@--",[NSThread currentThread]);
        //打印结果证明是在主线程
    }];
    //增加额外的任务,只有当任务数大于1的时候才会开异步执行
    [blockOp addExecutionBlock:^{
        NSLog(@"--%@--",[NSThread currentThread]);
    }];
    //自定义Operation:需要实现- (void)main方法,需要做的事情放在mian方法中
    
    //当创建一个队列,放到这个队列中的NSOperation对象会自动放到子线程中执行
    NSOperationQueue * queue = [[NSOperationQueue alloc]init];
    //创建一个主线程。放到里的对象也会自动在子线程中执行
    NSOperationQueue * mainQueue = [NSOperationQueue mainQueue];
    //设置最大并发数:同时执行任务的数量,3表示同时执行3个任务,
    queue.maxConcurrentOperationCount = 3;

3、队列的取消、暂停、恢复、优先级

    //- (void)cancelAllOperations;

    //- (void)waitUntilAllOperationsAreFinished;

    //取消所有队列,也可以单个取消队列,但是一旦开始就不能取消

    [mainQueue cancelAllOperations];

    //yes表示暂停、No表示恢复队列

    [mainQueue setSuspended:YES];

4、添加依赖

可以跨队列依赖,但是不能循环依赖,不管NSOperation对象在哪个队列,只要是两个NSOperation对象就可以依赖

    NSOperationQueue * queue = [[NSOperationQueue alloc]init];
    NSBlockOperation * block1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-----%@----",[NSThread currentThread]);
    }];
    NSBlockOperation * block2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-----%@----",[NSThread currentThread]);
    }];
    NSBlockOperation * block3 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-----%@----",[NSThread currentThread]);
    }];
    NSBlockOperation * block4 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-----%@----",[NSThread currentThread]);
    }];
    
    //添加依赖,block1和block2执行完成之后再执行block3,叫做block3依赖于block1和block2
    //给block3添加依赖
    [block3 addDependency:block1];
    [block3 addDependency:block2];
    //不能循环依赖,但可以跨队列依赖,不管是在哪个队列,只要是NSOperation对象就可以
    [block4 addDependency:block3];
    
    [queue addOperation:block1];
    [queue addOperation:block2];
    [queue addOperation:block3];
    [queue addOperation:block4];

5、线程间的通信:多张图片下载最后合成

-(void)downLoadImage
{
    __block UIImage * image1 = nil;
    __block UIImage * image2 = nil;
    NSOperationQueue * queue = [[NSOperationQueue alloc]init];
    NSBlockOperation * blcok1 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-----%@----",[NSThread currentThread]);
        image1 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@""]]];
    }];
    NSBlockOperation * blcok2 = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"-----%@----",[NSThread currentThread]);
        image2 = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@""]]];
    }];
    CGFloat imageH = imageView.bounds.size.height;
    CGFloat imageW = imageView.bounds.size.width;
    
    NSBlockOperation * block3 = [NSBlockOperation blockOperationWithBlock:^{
        //开启上下文
        UIGraphicsBeginImageContext(CGSizeMake(imageW, imageH));
        [image1 drawInRect:CGRectMake(0,0,imageW/2, imageH)];
        [image2 drawInRect:CGRectMake(0,imageW/2,imageW/2, imageH)];
        UIImage * image3 = UIGraphicsGetImageFromCurrentImageContext();
        //关闭上下文
        UIGraphicsEndImageContext();
        //回到主线程
        [[NSOperationQueue mainQueue]addOperation:[NSBlockOperation blockOperationWithBlock:^{
            self->imageView.image = image3;
        }]];

    }];
    //添加依赖
    [block3 addDependency:blcok1];
    [block3 addDependency:blcok2];
    
    //将任务添加到队列中
    [queue addOperation:blcok1];
    [queue addOperation:blcok2];
    [queue addOperation:block3];
}

五、多线程的应用

       SDWebImage框架的底层主要就是基于多线程,实现小图片的多图片下载,SDWebImag由两个缓存区,一个是内存层面上的,一个是硬盘层面上的,内存中是以key--value的形式存储图片,当没有内存空间的时候自动清理图片,文件是以时间为单位的,默认图片存储一周

  1、入口setImageWithURL:placeHolderImage:option:会先显示占位图片,然后根据URL处理图片

  2、进入SDWebImageMangaer的downLoadWithURL:delegate:option:userInfo方法交给SDImageCache,从缓存中查找图片是否已经存在,如果存在SDImageCacheDelegate回调imageCache:didFindImage:forkey到SDWebImageManager显示,如果没有

 3、如果内存的缓存中没有,生成NSINvocationOperation添加到队列中去硬盘中查找,根据key--value,如果找到了先加到缓存中(如果缓存中空闲内存过小,会先清空缓存)SDImageCacheDelegate回调方法   imageCache:didFindImage:forkeyPath显示图片

4、如果在硬盘中没有找到,说明图片不存在,需要下载,回调imageCache:didNotFindImage:forKeyPath

5、生成一个下载器:SDWebImageDownLoader开始下载,这一步由NSURLConnection完成,实现相关的delegate来判断下载完成、失败、下载中状态

6、下载完成后交给SDWebImagDecoder做图片的编码处理,是在NSOperationQueue中完成的

7、当完成后会调用imageDownloader:didFinishWithImage回调给SDWebImageManager告诉图片下载完成

8、在NSOperationQueue中分别将图片在主线程显示,在子线程中先保存到SDImageCache在保存到沙盒。

你可能感兴趣的:(原理篇,线程,GCD,NSOperation,多线程,NSThread)