多线程之GCD

一、GCD 核心概念

Grand Central Dispatch。它是苹果为多核的并行运算提出的解决方案,所以会自动合理地利用更多的CPU内核(比如双核、四核),最重要的是它会自动管理线程的生命周期(创建线程、调度任务、销毁线程),完全不需要我们管理,我们只需要告诉干什么就行。同时它使用的也是 c语言,不过由于使用了 Block(Swift里叫做闭包),使得使用起来更加方便,而且灵活。所以基本上大家都使用 GCD 这套方案,老少咸宜,实在是居家旅行、杀人灭口,必备良药。

  • GCD的优势

  • GCD是苹果公司为多核的并行运算提出的解决方案

  • GCD会自动利用更多的CPU内核(比如双核、四核)

  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)

  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

  • GCD中有2个核心概念

  • 任务:执行什么操作,任务使用 block 封装

  • 队列:用来存放(调度)任务

  • GCD的使用就2个步骤
  • 定制任务

  • 确定想做的事情

  • 将任务添加到队列中

  • GCD会自动将队列中的任务取出,放到对应的线程中执行

  • 任务的取出遵循队列的FIFO原则:先进先出,后进后出

  • 执行任务的函数
  • 异步 dispatch_async

1.不用等待当前语句执行完毕,就可以执行下一条语句
2.会开启线程执行 block 的任务
3.异步是多线程的代名词

  • 同步 dispatch_sync

1.必须等待当前语句执行完毕,才会执行下一条语句
2.不会开启线程
3.在当前执行 block 的任务

  • 同步和异步的区别

  • 同步:不开线程,必须等待当前语句执行完毕,才会执行下一条语句

  • 异步:开线程,不用等待当前语句执行完毕,就可以执行下一条语句

  • 队列 - 负责调度任务

  • 串行队列

1.一次只能"调度"一个任务
2.dispatch_queue_create("abc", NULL);

  • 并发队列

1.一次可以"调度"多个任务
2.dispatch_queue_create("abc", DISPATCH_QUEUE_CONCURRENT);

  • 主队列

1.专门用来在主线程上调度任务的队列
2.不会开启线程
3.在主线程空闲时才会调度队列中的任务在主线程执行
4.dispatch_get_main_queue()

  • 队列的选择
  • 多线程的目的:将耗时的操作放在后台执行!
  • 串行队列,只开一条线程,所有任务顺序执行

如果任务有先后执行顺序的要求
效率低 -> 执行慢-> "省电"
有的时候,用户其实不希望太快!例如使用 3G 流量,"省钱"

  • 并发队列,会开启多条线程,所有任务不按照顺序执行

如果任务没有先后执行顺序的要求
效率高 -> 执行快 -> "费电"
WIFI,包月

小结:

  • 开不开线程由执行任务的函数决定
    异步开,异步是多线程的代名词
    同步不开
  • (异步)开几条线程由队列决定
    ○ 串行队列开一条线程
    ○ 并发队列开多条线程,具体能开的线程数量由底层线程池决定
    iOS 8.0 之后,GCD 能够开启非常多的线程
    iOS 7.0 以及之前,GCD 通常只会开启 5~6 条线程
二.队列详解

1、串行队列

特点
以先进先出的方式,顺序调度队列中的任务执行。
无论队列中所指定的执行任务函数是同步还是异步,都会等待前一个任务执行完成后,再调度后面的任务。

创建
dispatch_queue_tqueue=dispatch_queue_create("abc",DISPATCH_QUEUE_SERIAL);
dispatch_queue_tqueue = dispatch_queue_create("abc", NULL);

1、串行队列
同步执行
/*
 提问:是否开线程?是否顺序执行?come here 的位置?
 结果:同步不开线程
*/
- (void)gcdDemo1{
 //创建串行队列
 dispatch_queue_tqueue = dispatch_queue_create("itcast.cn", DISPATCH_QUEUE_SERIAL);
 //同步执行任务

   for (int index = 0; index < 10; index ++) {
        dispatch_sync(queue, ^{
        NSLog(@"拼命下载中.....%@,i = %d",[NSThread currentThread],index);
  });
 }
        NSLog(@"come here");
}


2、串行队列  异步执行
/**
 提问:是否开线程?是否顺序执行?come here 的位置?
 结果: 会开线程,顺序执行
 */
-(void)gcdDemo2{
    //创建串行队列
   dispatch_queue_tqueue = dispatch_queue_create("itcast.cn", NULL);
    //异步执行任务
   for (int index = 0; index < 10; index ++) {
         dispatch_async(queue, ^{
          NSLog(@"拼命下载中.....%@,i = %d",[NSThread currentThread],index);
  });
    }
           NSLog(@"come here");
}

2、并发队列

特点
以先进先出的方式,并发调度队列中的任务执行。
如果当前调度的任务是同步执行的,会等待任务执行完成后,再调度后续的任务。
如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,会再新的线程调度后续任务的执行。

创建
dispatch_queue_t queue = dispatch_queue_create("cn.itcast", DISPATCH_QUEUE_CONCURRENT);

/**
  开不开线程由执行任务的函数决定
  — 同步不开
  - 异步开
 
  (异步)开几条线程由队列决定
 - 串行队列,只开一条
 - 并发队列,开多条,由底层线程池决定
 
 在iOS7.0之前,GCD底层线程池一般最多会开5到6条。
 在iOS8.0之后,GCD底层线程池要多少给多少。
 */

/**
 *  并发行队列异步执行
 *  问:会不会开线程? 开几条线程? 顺序执行?
    结果:会开,开多条(由底层线程池决定) 并发
 */
- (void)gcdDemo2 {
    // 创建并发队列
    dispatch_queue_t queue = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
    for (int i = 0; i < 10; ++i) {
        // 将任何添加到队列中
        dispatch_async(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"over");
}


/**
 *  并发队列同步执行
    问:会开线程吗?over是在后面还是前面输出?
    猜:不会 后面输出  
    全中
 */
- (void)gcdDemo1 {
    // 创建串行队列
    dispatch_queue_t queue = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);
    
    for (int i = 0; i < 10; ++i) {
        // 将任务添加到队列中--同步执行
        dispatch_sync(queue, ^ {
            NSLog(@"%@--%d",[NSThread currentThread],i);
        });
    }
    NSLog(@"over");
}

3、主队列

特点
Ø 专门用来在主线程上调度任务的队列。
Ø 不会开启线程。
Ø 以先进先出的方式,在主线程空闲时才会调度队列中的任务在主线程执行。
Ø 如果当前主线程正在有任务执行,那么无论主队列中当前被添加了什么任务,都不会被调度。

主队列的获取
Ø 主队列是负责在主线程调度任务的 
Ø 会随着程序启动一起创建
Ø 主队列只需要获取不用创建
dispatch_queue_t queue = dispatch_get_main_queue();


- (void)touchesBegan:(NSSet *)touches withEvent(UIEvent *)event {
     [self gcdDemo1];
     NSLog(@"睡");
     [NSThreadsleepForTimeInterval:1.0];
     NSLog(@"醒");

}

1、主队列,异步执行
- (void)gcdDemo1{
    dispatch_queue_tqueue = dispatch_get_main_queue();
   for (int i =0; i < 10;++i) {  
   dispatch_async(queue, ^{
      NSLog(@"%@ - %d", [NSThread currentThread], i); 
  });    
  NSLog(@"--->%d", i);
    }
    NSLog(@"come here");
}
提示:在主线程空闲时才会调度队列中的任务在主线程执行

 

2、主队列,同步执行
- (void)gcdDemo2{
 //获得主队列
    dispatch_queue_tqueue = dispatch_get_main_queue();
   NSLog(@"!!!");
    //同步执行任务
   dispatch_sync(queue, ^{
        NSLog(@"%@", [NSThreadcurrentThread]);
    });
    NSLog(@"come here");
}
**主队列和主线程相互等待会造成死锁**

 

3、主队列调度同步执行不死锁,把主队列的任务丢到子线程
- (void)gcdDemo3 {
    dispatch_queue_tqueue = dispatch_queue_create("com.itheima.queue",DISPATCH_QUEUE_CONCURRENT);
   void (^task)() = ^ {
        NSLog(@"%@===>",[NSThreadcurrentThread]);
       dispatch_sync(dispatch_get_main_queue(), ^{
         NSLog(@"死?");     
  });
   };
   dispatch_async(queue, task);
}
主队列在主线程空闲时才会调度队列中的任务在主线程执行

4、同步任务的作用

同步任务,可以让其他异步执行的任务,依赖某一个同步任务

例如:在用户登录之后,再异步下载文件!

- (void)gcdDemo1 {
    dispatch_queue_tqueue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);

    //所有的耗时操作都要异步执行
    //同步任务不执行完,队列不会调度后续的任务
   dispatch_sync(queue, ^{
        NSLog(@"登录 %@", [NSThread currentThread]);
    });

   dispatch_async(queue, ^{
        NSLog(@"下载 A %@",
[NSThreadcurrentThread]);
    });

   dispatch_async(queue, ^{

        NSLog(@"下载 B %@",
[NSThreadcurrentThread]);
    });
}

 

代码调整,让登录也异步执行

- (void)gcdDemo2 {
    dispatch_queue_tqueue = dispatch_queue_create("com.itheima.queue", DISPATCH_QUEUE_CONCURRENT);
   void (^task)() = ^{  
  dispatch_sync(queue, ^{
          NSLog(@"登录 %@", [NSThread currentThread]);  
  });     
  dispatch_async(queue, ^{ 
          NSLog(@"下载 A %@", [NSThread currentThread]);  
  });     
  dispatch_async(queue, ^{
          NSLog(@"下载 B %@", [NSThread currentThread]);   
  });
    };
   dispatch_async(queue, task);
}

5、全局队列

全局队列是系统为了方便程序员开发提供的,其工作表现与并发队列一致。
全局队列 异步任务
/**
 提问:是否开线程?是否顺序执行?come here的位置?
 */
- (void)gcdDemo8 {
    // 1. 队列
    dispatch_queue_t q = dispatch_get_global_queue(0, 0);

    // 2. 执行任务
    for (inti = 0; i < 10; ++i) {
        dispatch_async(q, ^{
            NSLog(@"%@ - %d", [NSThread currentThread], i);
        });
    }
    NSLog(@"come here");
}
运行效果与并发队列相同

 

全局队列和并发队列的比较

全局队列
没有名称
无论 MRC & ARC
都不需要考虑释放
日常开发中,建议使用"全局队列"

 
并发队列
有名字,和 NSThread 的 name 属性作用类似
如果在 MRC开发时,需要使用 dispatch_release(q); 释放相应的对象
dispatch_barrier 必须使用自定义的并发队列
开发第三方框架时,建议使用并发队列

 

 dispatch_get_global_queue函数参数

1.服务质量(队列对任务调度的优先级)/iOS 7.0 之前,是优先级

iOS 8.0(新增)
QOS_CLASS_USER_INTERACTIVE 0x21, 用户交互(希望最快完成-不能用太耗时的操作)
QOS_CLASS_USER_INITIATED 0x19, 用户期望(希望快,也不能太耗时)
QOS_CLASS_DEFAULT 0x15,  默认(用来底层重置队列使用的,不是给程序员用的)
QOS_CLASS_UTILITY 0x11,  实用工具(专门用来处理耗时操作!)
QOS_CLASS_BACKGROUND 0x09, 后台
QOS_CLASS_UNSPECIFIED 0x00,  未指定,可以和iOS 7.0 适配

iOS 7.0
DISPATCH_QUEUE_PRIORITY_HIGH 2  高优先级
DISPATCH_QUEUE_PRIORITY_DEFAULT 0  默认优先级
DISPATCH_QUEUE_PRIORITY_LOW (-2)  低优先级
DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN  后台优先级

2.为未来保留使用的,应该永远传入0

结论:如果要适配 iOS 7.0 & 8.0,使用以下代码: dispatch_get_global_queue(0,
0);

6、Barrier异步

注意:dispatch_barrier_async 必须使用自定义队列,否则执行效果和全局队列一致
Ø 主要用于在多个异步操作完成之后,统一对非线程安全的对象进行更新
Ø 适合于大规模的 I/O 操作

准备工作
@interface ViewController (){
    //加载照片队列
  dispatch_queue_t  _photoQueue;
}
@property (nonatomic, strong) NSMutableArray *photoList;
@end

 

@implementation ViewController
- (NSMutableArray *)photoList {
   if (_photoList == nil) {    
  _photoList = [[NSMutableArray alloc] init];
    }
    return_photoList;
}
提示:NSMutableArray 是非线程安全的。 

viewDidLoad 
- (void)viewDidLoad {
    [superviewDidLoad];
    _photoQueue= dispatch_queue_create("com.itheima.com", DISPATCH_QUEUE_CONCURRENT);

   for (int i =0; i < 330;++i) {    
  [self loadPhotos:i];
    }
    NSLog(@"come here");
} 

模拟下载照片并在完成后添加到数组

- (void)loadPhotos:(int)index
{
    dispatch_async(_photoQueue, ^{   
  NSString *fileName = [NSString stringWithFormat:@"%d.jpg", index %9];
     NSString *path = [[NSBundlemainBundle] pathForResource:fileNameofType:nil];   
     UIImage *image = [UIImage imageWithContentsOfFile:path];

      NSLog(@"下载照片%@,%d", fileName,index);
       [self.photoList addObject:image];
    });

}

由于 NSMutableArray 是非线程安全的,如果出现两个线程在同一时间向数组中添加对象,会出现程序崩溃的情况。 

使用dispatch_barrier_async修复崩溃情况
- (void)loadPhotos:(int)index
{
    dispatch_async(_photoQueue, ^{  
    NSString *fileName = [NSString stringWithFormat:@"%d.jpg", index %9];     
    NSString *path = [[NSBundle mainBundle] pathForResource:fileNameofType:nil];     
    UIImage *image = [UIImage imageWithContentsOfFile:path];
      NSLog(@"下载照片%@,%d", fileName,index);

        dispatch_barrier_async(_photoQueue, ^{
          NSLog(@"添加图片 %@,%@", fileName,[NSThread currentThread]);     
      [self.photoList addObject:image];   
  });
    });
}

使用 dispatch_barrier_async 添加的 block
会在之前添加的 block 全部运行结束之后,统一在同一个线程顺序执行,从而保证对非线程安全的对象进行正确的操作!

Barrier工作示意图


多线程之GCD_第1张图片
Snip20160427_7.png

7.一次性执行

有的时候,在程序开发中,有些代码只想从程序启动就只执行一次,典型的应用场景就是“单例”。

// MARK: 一次性执行
- (void)once {
   static dispatch_once_t onceToken;
   NSLog(@"%ld",onceToken);    
   dispatch_once(&onceToken, ^{
        [NSThread sleepForTimeInterval:1.0];
      NSLog(@"一次性吗?");
    });
    NSLog(@"come here");
}
dispatch
内部也有一把锁,是能够保证"线程安全"的!而且是苹果公司推荐使用的。
 
以下代码用于测试多线程的一次性执行
- (void)demoOnce {
   for (int i =0; i < 10;++i) {
        dispatch_async(dispatch_get_global_queue(0, 0), ^{  
      [self once];    
  });
    }
}

8.延迟操作

#pragma mark - 延迟执行
- (void)delay {
   /**
     从现在开始,经过多少纳秒,由"队列"调度  异步执行block 中的代码
     参数
     1. when    从现在开始,经过多少纳秒
     2. queue   队列
     3. block   异步执行的任务
  */

    dispatch_time_twhen = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0* NSEC_PER_SEC));
   void (^task)() = ^ {
        NSLog(@"%@", [NSThreadcurrentThread]);
    };
    //主队列
   dispatch_after(when, dispatch_get_main_queue(), task);
    //全局队列
//  dispatch_after(when, dispatch_get_global_queue(0, 0), task);
    //串行队列
//  dispatch_after(when, dispatch_queue_create("itheima", NULL),
task);
    NSLog(@"come here");

}

三.调度组

dispatch_group_async可以实现监听一组任务是否完成,完成后得到通知执行其他的操作。
比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了

- (void)group1 {
    //1. 调度组
    dispatch_group_tgroup = dispatch_group_create();
    
    //2. 队列
    dispatch_queue_tq = dispatch_get_global_queue(0, 0);

    //3. 将任务添加到队列和调度组
    dispatch_group_async(group, q, ^{
        [NSThread sleepForTimeInterval:1.0];
        NSLog(@"任务 1 %@",[NSThreadcurrentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"任务 2 %@",[NSThreadcurrentThread]);
    });
    dispatch_group_async(group, q, ^{
        NSLog(@"任务 3 %@",[NSThreadcurrentThread]);
    });   

    //4. 监听所有任务完成
    dispatch_group_notify(group, q, ^{
        NSLog(@"OVER %@", [NSThreadcurrentThread]);
    });
    
   //5. 判断异步
    NSLog(@"come here");
}

#pragma mark - dispatch_group_async 的实现原理
终端输入命令:man dispatch_group_async

The dispatch_group_async() convenience function behaves like so:
void
dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block)
{
    dispatch_retain(group);
    dispatch_group_enter(group);
    dispatch_async(queue, ^{
        block();
        dispatch_group_leave(group);
        dispatch_release(group);
    });
}

注意:进群组和离开群组一定要成对出现

你可能感兴趣的:(多线程之GCD)