Quantcast
Channel: CSDN博客移动开发推荐文章
Viewing all articles
Browse latest Browse all 5930

21day 多线程(GCD)

$
0
0

重点:
NSThread

多线程基础、pthread、开启线程的3种方式
线程的状态、线程安全问题、线程间的通信

GCD

同步方法和异步方法、队列的使用、线程间的通信
延迟执行、一次性代码、队列组、单例模式-ARC、单例模式-MRC、用宏抽取单例模式

NSOperation

NSOperation和NSOperationQueue的概念理解、NSInvocationOperation、NSBlockOperation
NSOperationQueue的常见方法、最大并发数、操作依赖、队列的取消\暂\恢

一、 GCD

libdispatch.tbd

#import <dispatch/dispatch.h>  主头文件

1、简介

GCD是Grand Central Dispatch的简称,它是基于C语言的。如果使用GCD,完全由系统管理线程,我们不需要编写线程代码。只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue)。GCD会负责创建线程和调度你的任务,系统直接提供线程管理。

1)GCD的2个重要概念是任务、队列

任务是”执行什么操作“

执行任务:

异步任务:dispatch_async(queue, ^{   NSLog(@"开启了一个异步任务,当前线程:%@", [NSThread currentThread]);  });  //可以在新的线程中执行任务,具备开启新线程的能力。--如果是主队列的话,将不开启新的线程
同步任务:dispatch_sync(queue, ^{      NSLog(@"开启了一个同步任务,当前线程:%@", [NSThread currentThread]);  });  //只能在当前线程中执行任务,不具备开启新线程的能力

队列:用来存放任务
1)队列类型:

 并发队列(concurrent dispatch queue则尽可能多地启动任务并发执行)。--并发功能只有在异步函数(dispatch_async)才有效。
串行队列(serial dispatch queue一次只能执行一个任务, 当前任务完成才开始出列并启动下一个任务)

小结

同步、异步的主要影响:能不能开启新的线程
并发、串行的主要影响:  任务的执行方式

2)GCD 使用的2个步骤:(最有价值的用法:将异步任务添加到并发队列中)

定制任务:确定想做的事情
将任务添加到队列中:GCD会自动将队列中的任务取出,放到对应的线程中执行。

2、创建和管理dispatch queue

1).获取并发队列

系统默认给每个应用提供了三个并发dispatch queue,整个应用内全局共享,三个queue的区别是优先级。
你不需要显式地创建这些queue,只需使用dispatch_get_global_queue函数来获取这三个queue:
// 获取默认优先级的全局并发dispatch queue 
dispatch_queue_t  queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
/*
第一个参数用于指定优先级,分别使用DISPATCH_QUEUE_PRIORITY_HIGH和DISPATCH_QUEUE_PRIORITY_LOW两个常量来获取高和低优先级的两个queue;第二个参数保留参数,默认0即可
*/

2)创建串行Dispatch Queue (serial dispatch queue) 两种方式:

方式一:

//    dispatch_queue_t queue = dispatch_queue_create("com.hisunpay.queue", NULL);
//如果是非ARC(automatic reference counting mode),需要释放创建的队列'release' is unavailable: not available in automatic reference counting mode
//    dispatch_release(queue);

方式二:使用主队列,即跟主线程相关联的队列,是GCD自带的一种特殊的串行队列。放到主队列的任务,都会放到主线程中执行--通常利用“主队列”来和主线程进行通信

#define HLWS(weakself) __typeof(&*self)(weakself)= self
#define HLGlobaQueue  dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define HLMainQueue   dispatch_get_main_queue()



//2)获取主队列
    dispatch_queue_t queue = dispatch_get_main_queue();
/** 线程间的通信*/
- (void) touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    //子线程下载图片
    __weak ViewController *vc = self;
    dispatch_async(HLGlobalQueue, ^{
        //下载图片
        NSString *url = @"http://avatar.csdn.net/6/4/8/1_q199109106q.jpg";
        UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url]]];
        //回到主线程
        dispatch_async(HLMainQueue, ^{
            //任务
//            [vc.button setBackgroundImage:image forState:UIControlStateNormal];
            [vc.button setImage:image forState:UIControlStateNormal];//       vc.button.buttonType=UIButtonTypeSystem 是不能设置image的
        });
    });
}

小结

这里写图片描述

p s: 1)主队列中不能添加同步任务,会导致死锁。

因为:同步任务A添加到当前正在执行的串行queue,会造成,新的同步任务A执行完才能结束正在执行的任务B,但当前执行的任务B执行结束之后才会执行新的同步任务A,就产生了死锁。

3、添加任务到queue
要执行一个任务,你需要将它添加到一个适当的dispatch queue,你可以单个或按组来添加,也可以同步或异步地执行一个任务。一旦进入到queue,queue会负责尽快地执行你的任务。
一般可以用一个block来封装任务内容。

1dispatch_syncdispatch_async。
添加任务到队列  Expand source


dispatch_sync(queue, ^{
       //任务
       NSLog(@"%s--开启了一个同步任务--%@",__func__,[NSThread currentThread]);//_block_invoke--开启了一个同步任务--<NSThread: 0x7f8f5b5014b0>{number = 1, name = main}
   });
   /* dispatch_async 可以在新的线程中执行任务,具备开启新线程的能力*/
   dispatch_async(queue, ^{
       //任务
       NSLog(@"%s--开启了一个异步任务%@",__func__,[NSThread currentThread]);//_block_invoke_2--开启了一个异步任务<NSThread: 0x7fa4a8473480>{number = 2, name = (null)}
   });

4、延迟执行
延迟执行的两种方法 Expand source

#pragma mark - 延迟执行总结
/** 使用GCD函数:
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(<#delayInSeconds#> * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
 <#code to be executed after a specified delay#>
 }); */
- (void)afterDelay3{
    /**主队列 */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%s-  -- thread:%@",__func__,[NSThread currentThread]);

    });

    /*
     dispatch_queue_priority_t.  并发队列,自动开启新线程
     */
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%s-  -- thread:%@",__func__,[NSThread currentThread]);//_block_invoke-  -- thread:<NSThread: 0x7a0180f0>{number = 2, name = (null)}
    });
}
/** 调用NSObject 的 - (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
*/
- (void)afterDelay1{
    //nvokes a method of the receiver on the current thread using the default mode after a delay. 不会卡住当前线程
    [self performSelector:@selector(downloadWithName:) withObject:@"http://" afterDelay:3];
}
- (void)afterDelay0{
    //不要使用sleep,缺点:卡住线程
    [NSThread sleepForTimeInterval:3];
}

5、一次性代码
dispatch_once函数 Expand source

/** dispatch_once 函数能保证:某段代码在程序运行过程中只被执行一次 */
- (void) downLoad{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //一次性代码
        NSLog(@"%s----下载图片",__func__);       
    });
}

6、暂停和继续queue()
我们可以使用dispatch_suspend函数暂停一个queue以阻止它执行block对象;
使用dispatch_resume函数继续dispatch queue。

1)调用dispatch_suspend会增加queue的引用计数,调用dispatch_resume则减少queue的引用计数。当引用计数大于0时,queue就保持挂起状态。因此你必须对应地调用suspend和resume函数。
2)挂起和继续是异步的,而且只在执行block之间(比如在执行一个新的block之前或之后)生效。挂起一个queue不会导致正在执行的block停止。

6、Dispatch Group的使用

dispatch_group_async函数将多个任务关联到一个Dispatch Group和相应的queue中,group会并发地同时执行这些任务。

而且Dispatch Group可以用来阻塞一个线程, 直到group关联的所有的任务完成执行

例子:合并下载的两张图片

   0、创建一个组组dispatch_group_create
     1、关联下载任务到group dispatch_group_async--下载两张图片放在同不同的任务中完成
     2、等待组中的任务执行完毕,回到主线程执行block回调  --dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行

组的使用 Expand source

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
    HLWS(vc);
    /*
     合并图片,
     0、创建一个组组dispatch_group_create
     1、关联一个任务到group dispatch_group_async--下载两张图片放在同不同的任务中完成
     2、等待组中的任务执行完毕,回到主线程执行block回调  --dispatch_group_notify函数用来指定一个额外的block,该block将在group中所有任务完成后执行
     */
    dispatch_async(HLGlobaQueue, ^{
        NSLog(@"%s--0-----%@",__func__,[NSThread currentThread]);//block_invoke--0-----<NSThread: 0x7b648350>{number = 2, name = (null)}
        //创建一个队列组

        dispatch_group_t group = dispatch_group_create();
        /*
         blocks 访问权限
         1)blocks可以访问局部变量a,但是不能修改。
         原因是在编译期间确定的,编译器编译的时候把a的值复制到block作为一个新变量(假设是a‘ = 10),此时a'和a是没有关系的。
         2)如果要修改就要加关键字:__block或者static*/
        __block UIImage *image1;
        static UIImage *image2;
        //下载图片任务关联到group
        dispatch_group_async(group, HLGlobaQueue, ^{
            //下载第一张图片
            NSLog(@"%s-- 1-%@",__func__,[NSThread currentThread]);//]_block_invoke3-- 1-<NSThread: 0x7b648350>{number = 2, name = (null)}
            NSString *str1 = @"http://avatar.csdn.net/6/4/8/1_q199109106q.jpg";
            image1=[vc imageWithUrl:str1];
        });
        dispatch_group_async(group, HLGlobaQueue, ^{
            NSLog(@"%s--2-----%@",__func__,[NSThread currentThread]);//_block_invoke11--2-----<NSThread: 0x7c9553b0>{number = 3, name = (null)}
            NSString *str2 = @"http://avatar.csdn.net/D/9/E/2_u011018979.jpg";
            image2= [vc imageWithUrl:str2];
        });

        //3.图片下载完之后开始绘制图片--        // 等待组中的任务执行完毕,回到主线程执行block回调
        dispatch_group_notify(group, HLGlobaQueue, ^{
            NSLog(@"%s-- 3-%@",__func__,[NSThread currentThread]);//_block_invoke19-- 3-<NSThread: 0x7b2525c0>{number = 2, name = (null)}
            //绘制图片
            //开启上上文
            UIGraphicsBeginImageContextWithOptions(image1.size, NO, 0.0);
            [image1 drawInRect:CGRectMake(0, 0, image1.size.width, image1.size.height)];
            [image2 drawInRect:CGRectMake(image1.size.width-image2.size.width*0.5, image1.size.height-image2.size.height*0.5, image2.size.width*0.5, image2.size.height*0.5)];
            //获取图片
            UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
            //关闭上下文
            UIGraphicsEndImageContext();

            //回到主队列
            dispatch_async(HLMainQueue, ^{
                NSLog(@"%s-- 4-%@",__func__,[NSThread currentThread]);//_block_invoke_2-- 4-<NSThread: 0x7a650590>{number = 1, name = main}
                //显示图片
                [vc.imageView setImage:image];
            });
        });
    });
}
作者:u011018979 发表于2017/7/4 10:18:30 原文链接
阅读:10 评论:0 查看评论

Viewing all articles
Browse latest Browse all 5930

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>