多线程之GCD,NSOperation和NSOperationQueue,NSThread

多线程的缺点:

1.拥有多个可执行的路径

2.线程问题难以调试

3.数据安全受到一定的影响,一个存,一个删的时候;

方法一  、

GCD:GCD队列始终是FIFO(先进先出)的方法来处理任务,但是任务执行的时间并不相同,因此先处理的任务不一定先结束,所以用底层的线程池来处理和管理用户提交的任务,串行队列线程池之需要维护一个线程即可,并发队列线程池需要维护多个线程;

创建队列和访问队列:

dispatch_queue_t  dispatch_get_current_queue()获取当前执行代码所在队列

dispatch_queue_t dispatch_get_global_queue(0, 0)全局并发队列,参数1:优先级 (#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:预留参数;

dispatch_queue_t dispatch_get_main_queue()主线程关联的串行队列;

char * name= dispatch_queue_get_label(queue);获取指定队列标签;


创建一个队列

    dispatch_queue_t que=dispatch_queue_create("createQueue", DISPATCH_QUEUE_SERIAL);创建一个串行队列,串行queue确保任务按可预测的顺序执行。而且只要你异步地提交任务到串行queue,就永远不会产生死锁

    dispatch_queue_t que1=dispatch_queue_create("createrQueue", DISPATCH_QUEUE_CONCURRENT);创建一个并行队列;



开启一个子线程,

1.

1> GCD提供一个特殊的dispatch queue,可以在应用的主线程中执行任务。只要应用主线程设置了run loop(CFRunLoopRef类型或NSRunLoop对象管理),就会自动创建这个queue,并且最后会自动销毁。非Cocoa应用如果不显式地设置run loop, 就必须显式地调用dispatch_main函数来显式地激活这个dispatch queue,否则虽然你可以添加任务到queue,但任务永远不会被执行。

2> 调用dispatch_get_main_queue函数获得应用主线程的dispatch queue,添加到这个queue的任务由主线程串行化执行

3> 代码实现,比如异步下载图片后,回到主线程显示图片

dispatch_async(dispatch_get_global_queue(0, 0), ^{

      //dispatch_get_global_queue(0, 0)获取系统的全局并发队列;

//进行一些耗时的操作; 

 

  dispatch_async(dispatch_get_main_queue(), ^{

      // dispatch_get_main_queue() 获取主线程关联的串行队列;

//主线程中更新UI,假如允许子线程访问,修改UI,这就需要对多个新线程的并发访问进行同步控制,否则,多个线程将破坏UI的完整性;

  });

 });

2.

 // 一次性执行:

 static dispatch_once_t onceToken;// 该静态变量用于标识是否已经执行;

 dispatch_once(&onceToken, ^{

     // code to be executed once

 });

3.

 // 延迟2秒执行:

 double delayInSeconds = 2.0;

 dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC);

 dispatch_after(popTime, dispatch_get_main_queue(), ^(void){

     // code to be executed on the main queue after delay

 });

4.

// 自定义dispatch_queue_t

 dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);

 dispatch_async(urls_queue, ^{  

   // your code 

 });

 dispatch_release(urls_queue);

5. // 合并汇总结果

Dispatch Group的使用

假设有这样一个需求:从网络上下载两张不同的图片,然后显示到不同的UIImageView上去,一般可以这样实现



  1. // 根据url获取UIImage  
  2. - (UIImage *)imageWithURLString:(NSString *)urlString {  
  3.     NSURL *url = [NSURL URLWithString:urlString];  
  4.     NSData *data = [NSData dataWithContentsOfURL:url];  
  5.     return [UIImage imageWithData:data];  
  6. }  
  7.   
  8. - (void)downloadImages {  
  9.     // 异步下载图片  
  10.     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  11.         // 下载第一张图片  
  12.         NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";  
  13.         UIImage *image1 = [self imageWithURLString:url1];  
  14.           
  15.         // 下载第二张图片  
  16.         NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";  
  17.         UIImage *image2 = [self imageWithURLString:url2];  
  18.           
  19.         // 回到主线程显示图片  
  20.         dispatch_async(dispatch_get_main_queue(), ^{  
  21.             self.imageView1.image = image1;  
  22.               
  23.             self.imageView2.image = image2;  
  24.         });  
  25.     });  
  26. }  

虽然这种方案可以解决问题,但其实两张图片的下载过程并不需要按顺序执行,并发执行它们可以提高执行速度。有个注意点就是必须等两张图片都下载完毕后才能回到主线程显示图片。Dispatch Group能够在这种情况下帮我们提升性能。下面先看看Dispatch Group的用处:


我们可以使用dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理。

下面用Dispatch Group优化上面的代码:



  1. // 根据url获取UIImage  
  2. - (UIImage *)imageWithURLString:(NSString *)urlString {  
  3.     NSURL *url = [NSURL URLWithString:urlString];  
  4.     NSData *data = [NSData dataWithContentsOfURL:url];  
  5.     // 这里并没有自动释放UIImage对象  
  6.     return [[UIImage alloc] initWithData:data];  
  7. }  
  8.   
  9. - (void)downloadImages {  
  10.     dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  11.       
  12.     // 异步下载图片  
  13.     dispatch_async(queue, ^{  
  14.         // 创建一个组  
  15.         dispatch_group_t group = dispatch_group_create();  
  16.           
  17.         __block UIImage *image1 = nil;  
  18.         __block UIImage *image2 = nil;  
  19.           
  20.         // 关联一个任务到group  
  21.         dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  22.             // 下载第一张图片  
  23.             NSString *url1 = @"http://car0.autoimg.cn/upload/spec/9579/u_20120110174805627264.jpg";  
  24.             image1 = [self imageWithURLString:url1];  
  25.         });  
  26.           
  27.         // 关联一个任务到group  
  28.         dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  29.             // 下载第一张图片  
  30.             NSString *url2 = @"http://hiphotos.baidu.com/lvpics/pic/item/3a86813d1fa41768bba16746.jpg";  
  31.             image2 = [self imageWithURLString:url2];  
  32.         });  
  33.           
  34.         // 等待组中的任务执行完毕,回到主线程执行block回调  
  35.         dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
  36.             self.imageView1.image = image1;  
  37.             self.imageView2.image = image2;  
  38.               
  39.             // 千万不要在异步线程中自动释放UIImage,因为当异步线程结束,异步线程的自动释放池也会被销毁,那么UIImage也会被销毁  
  40.               
  41.          
  42.         });  
  43.           
  44.   
  45.      
  46.     });  
  47. }  

dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行

6.并发执行循环迭代,提高性能

  1. int i;  
  2. int count = 10;  
  3. for (i = 0; i < count; i++) {  
  4.    printf("%d  ",i);  
  5. }  
  6. 使用dispatch_apply替换了for循环,你传递的block必须包含一个size_t类型的参数,用来标识当前循环迭代。第一次迭代这个参数值为0,最后一次值为count - 1
  7. =====================================================================================
  8. 1 // 获得全局并发queue  
  9. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  10. size_t count = 10;  
  11. dispatch_apply(count, queue, ^(size_t i) {  
  12.     printf("%zd ", i);  
  13. });  
  14. // 销毁队列  
  15. dispatch_release(queue);  
  16. 打印结果;
  17.      Pasted Graphic.tiff
  18. 可以看出,这些迭代是并发执行的


  1. 和普通for循环一样,dispatch_applydispatch_apply_f函数也是在所有迭代完成之后才会返回,因此这两个函数会阻塞当前线程,主线程中调用这两个函数必须小心,可能会阻止事件处理循环并无法响应用户事件。所以如果循环代码需要一定的时间执行,可以考虑在另一个线程中调用这两个函数。如果你传递的参数是串行queue,而且正是执行当前代码的queue,就会产生死锁。
  2. 7.暂停和继续queue
  3. 我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;使用dispatch_resume函数继续dispatch queue。调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0,queue就保持挂起状态。因此你必须对应地调用suspendresume函数。挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。


GCD的另一个用处是可以让程序在后台较长久的运行。

1.当应用进入后台时,如果程序有些状态数据没有保存,而IOS可能在内存紧张的时候终止该应用程序,那么久可能导致应用丢失状态数据的情况,

2.当应用程序转入后台后,不要在主线程中执行超过5秒的任务,这是应用有可能从内存中被删除了;

3.如果进入后台后,程序正在执行下载或文件传输,告诉系统进入后台后还有更多任务需要完成,从而向系统申请更多的时间,这样当我们的程序进入后台后,及时用户在使用其它应用的时候,只要系统还有足够的内存,我们的应用就可以保存在内存中;

向后台请求更多时间;

在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。

让程序在后台长久运行的示例代码如下:


- (void)viewDidLoad {

    [super viewDidLoad];

    //监听是否进入后台;

    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(enterBack:) name:UIApplicationDidEnterBackgroundNotification object:[UIApplication sharedApplication]];

   


}


-(void)enterBack:(NSNotification*)notification

{

    UIApplication * app=[UIApplication sharedApplication];

    

    __block UIBackgroundTaskIdentifier backTaskId;//定义标识

    //向系统请求更多后台执行时间;

    backTaskId=[app beginBackgroundTaskWithExpirationHandler:^{

        [app endBackgroundTask:backTaskId];

    }];

    if (backTaskId==UIBackgroundTaskInvalid) {

        NSLog(@"IOS版本不支持后台运行,后台任务启动失败");

        return;

    }

    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        for (int i=0; i<100; i++) {

            NSLog(@"下载任务完成:%d",i);

//            模拟正在下载的操场;

            [NSThread sleepForTimeInterval:10];

            

        }

       

        NSLog(@"剩余的后台任务时间为:%f", app.backgroundTimeRemaining);

        //结束后台任务

        [app endBackgroundTask:backTaskId];

    });

}





方法二、

NSOperationNSOperationQueue(在GCD的基础上做的,比GCD稍慢)


NSOperationQueue:底层维护一个线程池,是一个FIFO(先进先出)的队列,它负责提交多个NSOperation操作

NSOperation: nsoperation一般不会拿来直接使用,而是选择它的子类NSInvocationOperation NSBlockOperation

  NSString * url=@"http://www.gg/logo.jpg";

    NSInvocationOperation * operation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(downloadImageWithUrl:) object:url];

-(void)downloadImageWithUrl:(NSString*)url

{}

03E40DA2B7AEDF9247E48EC71262F9E8.jpg

方法三,更轻量级,更偏向底层的多线程实现方法,需要自己管理线程的生命周期,线程的同步;

NSthread:

- (void)viewDidLoad {

    [super viewDidLoad];

    for (int i=0; i<100; i++) {

        NSLog(@"====%@===%d",[NSThread currentThread],i);

        if (i==20) {

            //init创建的线程必须调用start方法启动进入准备状态,等待系统调度,调度是具有随机性的;

            NSThread * thread=[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil];

            [thread start];

            //detachNewThreadSeletor创建的同时就启动进入准备状态,等待系统调度;

            [NSThread detachNewThreadSelector:@selector(run) toTarget:self withObject:nil];

        }

    }

    }

-(void)run{

    for (int i=0; i<100; i++) {

       NSLog(@"------%@----%d",[NSThread currentThread],i);

    }

}

Pasted Graphic_1.tiffs

线程状态:

当线程被创建并被启动后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,当进入执行状态后也不是一直占用CUP独自运行,所以CPU需要在多个线程之间切换,线程状态也会多次在运行,准备状态之间切换

如果希望线程启动时就直接进入执行状态,程序可以使用 [NSThread sleepForTimeInterval:0.001];让运行的线程进入阻塞状态(主线程)睡眠1毫秒——1秒就够了,因为在这1毫秒内CPU是不会空闲的,它回去执行处于准备状态的线程,这样就可以让创建的子线程立即执行


线程的终止

1.正常结束终止,2,错误终止,3调用exit方法终止;

测试某个线程是否在运行,调用线程对象的isExcutiong.isFinished方法;

NSThread并没有提供方法来终止某个子线程,要终止线程先调用[thread cancel],改变线程状态,利用isCancelled,判断状态,如果为Yes,调用[NSThread exit];

真正终止线程;

线程睡眠:如果要让线程暂停一段时间,并进入阻塞状态,[NSThread sleepForTimeInterval:0.001],[NSThread sleepUntilDate:]

线程优先级:线程优先级double(0-1.0,每个子线程的默认优先级为0.5;thread.name=@"A"; thread.threadPriority=0.8;值越大优先级越高;

线程同步:

@sychronized

{ //需要进行同步处理的代码}

代码块可以实现同步,保证代码安全性,任何时刻只能有一个线程可以获得对同步监视器的锁定,当同步代码块执行完成后,该对象会释放对同步监视器的锁定;

同步锁:

  NSLock * lock=[[NSLock alloc]init];

    [lock lock];

   

    [lock unlock];

使用NSCondition控制线程同步通信:NSCondition 可以让那些已经锁定的NSCondition对象却无法继续使用执行的线程释放NSCondition对象,NSCondition对象也可以唤醒其他处于等待状态的线程;

-wait  :导致线程当前线程一直等待,直到其他线程调用NSCondition的signal方法或broadcast方法唤醒该线程,waitUntilDtae:

-signal:唤醒等待的单个线程;选择是任意性的,

-broadcast:唤醒所有线程;

#import "Acount.h"


@implementation Acount

-(instancetype)init

{

    self=[super init ];

    if (self) {

        _cond=[NSCondition new];

    }

    return self;

}


-(instancetype)initWithCountNo:(NSString *)acountNo balance:(float)balance

{

    self=[super init];

    if (self) {

        _cond=[NSCondition new];

        _acountNo=acountNo;

        _balance=balance;

        

    }

    return self;

}

-(void)draw:(float)drawNum

{

    [_cond lock];

    //flag=no没钱取钱阻塞;

    if (drawNum>_balance) {

        _flag=NO;

    }

    else

    {

        _flag=YES;

    }

    if (!_flag) {

        NSLog(@"等待存钱drawNum:%f,_balance:%f",drawNum,_balance);

        [_cond wait];

        

    }else

    {

        

        NSLog(@"%@取钱:%g", [NSThread currentThread].name,drawNum);

        _balance-=drawNum;

        NSLog(@"账户余额:%g",_balance);

        [_cond broadcast];

    }

    [_cond unlock];

}

-(void)deposit:(float)depositNum

{

    [_cond lock];

  

         NSLog(@"%@存钱:%g", [NSThread currentThread].name,depositNum);

        _balance+=depositNum;

          NSLog(@"账户余额:%g",_balance);

        [_cond broadcast];

    

    [_cond unlock];

    

}

===============================================================================================================================

- (void)viewDidLoad {

    [super viewDidLoad];

    _account=[[Acount alloc]initWithCountNo:@"123" balance:1000.0];


}

- (IBAction)DepositAndDraw:(id)sender {

    [NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self withObject:[NSNumber numberWithDouble:800.0]];

    [NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self withObject:[NSNumber  numberWithDouble:800.0]];

    [NSThread detachNewThreadSelector:@selector(drawMethod:) toTarget:self withObject:[NSNumber  numberWithDouble:800.0]];

    [NSThread detachNewThreadSelector:@selector(depositMethod:) toTarget:self withObject:[NSNumber  numberWithDouble:800.0]];


}

-(void)depositMethod:(NSNumber*)depositNum

{

    [NSThread currentThread].name=@"A存钱";

    for (int i=0; i<100; i++) {

        [_account deposit:[depositNum floatValue]];

    }

}

-(void)drawMethod:(NSNumber*)drawNum

{

     [NSThread currentThread].name=@"B取钱";

    for (int i=0 ; i<100; i++) {

        [_account draw:[drawNum floatValue]];

    }

}

1__#$!@%!#__Pasted Graphic.tiff




你可能感兴趣的:(多线程之GCD,NSOperation和NSOperationQueue,NSThread)