>

同步执行每个任务不完成的时候不会执行下一个

- 编辑:澳门博发娱乐官网 -

同步执行每个任务不完成的时候不会执行下一个

GCD: GCD的核心概念就是把任务添加到队列中,指定任务执行的方法.1 :任务,使用Block封装好的代码段就是一个任务.2 :队列,分串行队列,并发队列2种:串行: 1个一个的调任务,主线程也是串行队列并发: 可以同时调度多个任务,3 :执行: 执行又分同步执行和异步执行:同步: 同步执行每个任务不完成的时候不会执行下一个命令异步: 当前任务不完成一样可以执行下一个命令.

1. GCD的特点

  • GCD会自动利用更多的CPU内核
  • GCD自动管理线程的生命周期(创建线程,调度任务,销毁线程等)
  • 程序员只需要告诉 GCD 想要如何执行什么任务,不需要编写任何线程管理代码

GCD:

特点:自动管理线程的生命周期(创建线程、调度任务、销毁线程),使用C语言,自带Block。灵活,方便,功能强大。

  • Queue队列
  • 串行队列 FIFO(First Input First Output) 遵循先入先出原则的队列
    Serial: tasks execute one at a time in FIFO order
dispatch_queue_t queue = dispatch_queue_create("SerialQueue",DISPATCH_QUEUE_SERIAL)
  • 并发队列
    Concurrent: tasks are dequeued in FIFO order, but run concurrently and can finish in any order.
dispatch_queue_t  queue =  dispatch_queue_create ( "ConCurrentQueue" ,  DISPATCH_QUEUE_CONCURRENT );
  • 全局队列 GCD默认提供的全局并发队列,获取
dispatch_queue_t q = dispatch_get_global_queue(long identifier, unsigned long flags)

参数详解:
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,                    未指定

unsigned long flags:苹果官方文档是这样解释的: Flags that are reserved for future use。标记是为了未来使用保留的!所以这个参数应该永远指定为0

  • 主队列 主队列是特殊队列,与主线程一样,程序启动时就存在,所以不需要创建,我们要做的就是直接获取它
dispatch_queue_t queue = dispatch_get_main_queue();
  • 任务

  • dispatch_async 异步: 当前线程会继续执行,不会阻塞当前线程

  • dispatch_sync 同步: 阻塞当前线程 等待block中的任务执行完毕

  • tips:有人说async和sync的区别是是否会另开线程,这样说是不严谨的,在下文常见问题中会详述

  • 常见问题:

  • 主队列异步

NSLog(@"1");
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");

打印结果:132
然而这样貌似并不清晰,再来看下面

 NSLog(@"%@", [NSThread currentThread]);
NSLog(@"1");
    dispatch_async(dispatch_get_main_queue(), ^{

        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@", [NSThread currentThread]);
        NSLog(@"2");
    });
    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"3");

打印结果:

SomeThing[47574:2035090] <NSThread: 0x7fdebbd04e30>{number = 1, name = main}
SomeThing[47574:2035090] 1
SomeThing[47574:2035090] <NSThread: 0x7fdebbd04e30>{number = 1, name = main}
SomeThing[47574:2035090] 3
SomeThing[47574:2035090] <NSThread: 0x7fdebbd04e30>{number = 1, name = main}
SomeThing[47574:2035090] 2

结论:主队列异步,没有另开线程,所以在主队列中,即使有异步操作,主队列依然会遵循FIFO原则在主线程串行执行(上文说到同步与异步的区别是是否开启新线程这一说法不严谨,这里我们可以看到,异步不一定会开启新线程,看完下面几个例子,还会验证同步一定会阻塞当前线程)

  • 全局队列异步
NSLog(@"1");    
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"2");
    });    
    NSLog(@"3");

打印结果:132
再来看下面:

NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"1");

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"%@", [NSThread currentThread]);
        NSLog(@"2");
    });

    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"3");

打印结果:

SomeThing[47614:2040514] <NSThread: 0x7fa70b6017a0>{number = 1, name = main}
SomeThing[47614:2040514] 1
SomeThing[47614:2040554] <NSThread: 0x7fa70b52bb50>{number = 2, name = (null)}
SomeThing[47614:2040554] 2
SomeThing[47614:2040514] <NSThread: 0x7fa70b6017a0>{number = 1, name = main}
SomeThing[47614:2040514] 3

结论:全局队列异步,另开线程,2、3执行顺序理论上是不确定的

  • 主队列同步
NSLog(@"1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"2");
    });
    NSLog(@"3");

打印结果:1
结论:线程死锁,因为同步任务有一个特点就是添加即立即执行,此时主队列还有3没有执行,主线程又会因为同步任务等待主队列调度任务,主队列等待分配同步任务,而同步任务之前,主队列还有任务没有执行,这种相互等待就会造成线程死锁,主线程的死锁结果是UI卡死

  • 全局队列同步
NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"1");

    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        [NSThread sleepForTimeInterval:2.0f];
        NSLog(@"%@", [NSThread currentThread]);
        NSLog(@"2");
    });

    [NSThread sleepForTimeInterval:2.0f];
    NSLog(@"%@", [NSThread currentThread]);
    NSLog(@"3");

打印结果:

SomeThing[2891:278388] <NSThread: 0x7f82b1d06c00>{number = 1, name = main}
SomeThing[2891:278388] 1
SomeThing[2891:278388] <NSThread: 0x7f82b1d06c00>{number = 1, name = main}
SomeThing[2891:278388] 2
SomeThing[2891:278388] <NSThread: 0x7f82b1d06c00>{number = 1, name = main}
SomeThing[2891:278388] 3

结论:阻塞当前线程(主线程)先执行block内任务,此处代码与主队列同步非常相似,区别仅在于主队列和全局队列,而这里为什么不会造成线程死锁呢?很简单,一步一步来分析,因为这里不会造成“相互等待”,同步任务添加至全局异步队列并立即执行,主队列等待同步任务执行结束,回到主队列继续执行任务3。会造成困惑往往是因为在不经意间把主队列与主线程混淆了。

  • 组合问题一
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1"); 
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"%@", [NSThread currentThread]);
            [NSThread sleepForTimeInterval:2];
            NSLog(@"2"); 
        });
        NSLog(@"3"); 
    });
    NSLog(@"4"); 

结果:4123或1423
分析:
1)异步操作1和4都有可能先完成,注意在实际开发中的大段代码中我们要注意到4的存在
2)多线程开发中要时时刻刻确认当前线程是什么,1->2或4->2主队列同步,主队列任务一定会回到主线程执行,所以当前线程由子线程回到主线程
3)同步一定会阻塞当前线程,这里的当前线程是回到主线程之前的子线程,所以2->3
4)这里的主线程同步依然不会造成死锁,为什么呢?因为线程死锁的根本原因是“相互等待”,而这里,等待同步任务的线程是线程2,而不是主线程,所以不会造成死锁。

  • 组合问题二
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"1"); // 任务1
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"2"); // 任务2
        });
        NSLog(@"3"); // 任务3
    });
    NSLog(@"4"); // 任务4
    while (1) {}
    NSLog(@"5"); // 任务5

结果:14或41
分析:while()循环会直接导致死锁

  • 总结:关于GCD中这几个典型问题的分析,只要把握几个原则就可以清晰的理解了,首先,串行队列一定遵循FIFO原则;然后,明确线程和队列的概念,线程是线程,队列是队列;还有,一定要明确当前线程是什么;最后,明确串行队列和并发队列的区别,是否阻塞当前线程,是否会开启新线程。
  • 子线程回到主线程
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 执⾏耗时的异步操作...
        dispatch_async(dispatch_get_main_queue(), ^{
            // 回到主线程,执⾏UI刷新操作
        });
    });
  • 延迟执行
    1 NSObject方法
[self performSelector:@selector(run) withObject:nil afterDelay:2.0];

注意点:该方法在哪个线程调用,就在那个线程执行
2 GCD函数

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    // 2秒后异步执行这里的代码...
});

注意点:dispatch_after可以通过指定队列来控制在哪个线程执行,主队列就在主线程,并发队列就新开线程

  • 一次性代码
    在程序运行中只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});

GCD实现的单例模式

//初始化
-(instancetype)init{
    // 只进行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _instance = [super init];
    });
    return _instance;
}
//调用
+(instancetype)sharedSingleton{
 return [[self alloc] init];
}
//copy底层会调用copyWithZone
-(id)copyWithZone:(NSZone *)zone{ 
return _instance;
}
  • dispatch_group_async
    dispatch_group_async可以实现对一组并发任务的监听,比如10个并发任务,全部完成后回到主线程进行下一步:
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();

    for (int i = 0; i < 10; i++) {
        dispatch_group_async(group, queue, ^{
            [NSThread sleepForTimeInterval:i];
            NSLog(@"-%d", i);
        });
    }

    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        //更新界面
    });
  • 队列组

概念解释

技 术 文 章 / 超 人

队列类型:

// 队列类型dispatch_queue_t

2. GCD的基本概念

  • ###### 任务(block):

任务就是将要在线程中执行的代码,将这段代码用block封装好,然后将这个任务添加到指定的执行方式(同步执行和异步执行),等待CPU从队列中取出任务放到对应的线程中执行。

  • ###### 同步(sync):

一个接着一个,前一个没有执行完,后面不能执行,不开线程。
异步(async):开启多个新线程,任务同一时间可以一起执行。异步是多线程的代名词

  • ###### 队列:

装载线程任务的队形结构。(系统以先进先出的方式调度队列中的任务执行)。在GCD中有两种队列:串行队列和并发队列。

  • ###### 并发队列:

线程可以同时一起进行执行。实际上是CPU在多条线程之间快速的切换。(并发功能只有在异步(dispatch_async)函数下才有效)

  • ###### 串行队列:

线程只能依次有序的执行。

GCD总结:
将任务(要在线程中执行的操作block)添加到队列(自己创建或使用全局并发队列),并且指定执行任务的方式(异步dispatch_async,同步dispatch_sync)

NSThread:

1. 执行任务的函数:在GCD中,任务是通过 block来封装的,并且任务的block没有参数也没有返回值。

同步:你必须把我的代码执行完你再走,一定要执行完同步里的代码再执行下面的代码

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

异步:你先走执行我下面的代码,我找人、找线程去执行我里面的代码

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

创建一个队列:
// dispatch_queue_t 返回值队列// const char *label 队列名字// dispatch_queue_attr_t attr 队列类型dispatch_queue_tdispatch_queue_create(const char *label, dispatch_queue_attr_t attr);

dispatch_queue_attr_t 队列类型:

DISPATCH_QUEUE_SERIAL : 串行队列 ,等待执行中的处理结果.

DISPATCH_QUEUE_CONCURRENT : 并发队列,不等待执行中的处理结果.

实例:创造一个队列

dispatch_queue_t que = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);

获得一个全局的并发队列:

// dispatch_queue_t 返回值一个队列// long identifier 队列的优先级// unsigned long flags 传 0dispatch_queue_tdispatch_get_global_queue(long identifier, unsigned long flags);

队列的优先级:

ios7#define DISPATCH_QUEUE_PRIORITY_HIGH 2 高级#define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 默认#define DISPATCH_QUEUE_PRIORITY_LOW  低级#define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN 后台ios8QOS_CLASS_USER_INTERACTIVE 用户希望线程快点执行完毕,不要使用耗时操作QOS_CLASS_USER_INITIATED 用户需要的,不要使用耗时操作QOS_CLASS_DEFAULT 默认QOS_CLASS_UTILITY 耗时操作QOS_CLASS_BACKGROUND 后台QOS_CLASS_UNSPECIFIED 0 未指定优先级

实例:

dispatch_queue_t queget = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);

获取一个当前的主队列:

// dispatch_queue_t 队列 这里返回的是主队列dispatch_queue_tdispatch_get_main_queue

实例:

dispatch_queue_t mainque = dispatch_get_main_queue();

3. 队列的创建方法

  • 使用dispatch_queue_create来创建队列对象,传入两个参数,第一个参数表示队列的唯一标识符,可为空。第二个参数用来表示串行队列(DISPATCH_QUEUE_SERIAL)或并发队列(DISPATCH_QUEUE_CONCURRENT)。
// 串行队列
dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);
// 并发队列
dispatch_queue_t queue1 = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
  • GCD的队列还有另外两种

NSOpreation

NSOperation 是苹果公司对 GCD 的封装,完全面向对象,所以使用起来更好理解。NSOperation和NSOperationQueue分别对应 GCD 的 任务 和 队列。

2. GCD使用步骤:

第一步: 创建/获取 队列
第二步: 创建任务,确定要做的事情
第三步: 将任务添加到队列中
(1)GCD会自动将队列中的任务取出,放到对应的线程中执行
(2)任务的取出遵循队列的FIFO原则: 先进先出,后进后出

GCD 介绍
Grand Central Dispatch(GCD) 是Apple开发的一个多核编程的较新的解决方法,可译为“牛逼的中枢调度器”纯C语言,提供了非常多强大的函数。它主要用于优化应用程序以支持多核处理器以及其他对称处理系统,GCD会自动利用更多的CPU内核(比如双核、四核)。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用,iOS7时能开启5、6条子线程, iOS8以后可以50、60条子线程。

线程之间的执行顺序:

主队列:

主队列负责在主线程上调度任务,如果在主线程上已经有任务正在执行,主队列会等到主线程空闲后再调度任务。通常是返回主线程更新UI的时候使用。dispatch_get_main_queue()

dispatch_async(dispatch_get_global_queue(0, 0), ^{
      // 耗时操作放在这里
      dispatch_async(dispatch_get_main_queue(), ^{
          // 回到主线程进行UI操作
      });
  });

3. 队列

包括: 串行队列、并发队列、主队列、全局队列

GCD的优势

同步执行:
voiddispatch_sync(dispatch_queue_t queue, dispatch_block_t block);

同步串行实例: 这里的代码将 封装的代码一个一个放入到que的队列中去执行,因为是串行的所以会一个一个执行,因为是同步执行,所以在一个执行完以前不会执行下一个.并且不会开新线程.

图片 1串行同步.png

dispatch_queue_t que = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL);for (int i = 0 ; i<10; i++) { dispatch_sync(que, ^{ NSLog(@"%@",[NSThread currentThread]); });}

同步并发实例:这里的代码将 封装的代码一个一个放入到que的队列中去执行,虽然是并发队列了,但是因为是同步执行的原因,在一个代码执行完成之前,不能执行另外一个任务,所以仍然一个一个执行,并且不会开启新的线程.

图片 2并发同步.png

dispatch_queue_t que = dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT);for (int i = 0 ; i<10; i++) { dispatch_sync(que, ^{ NSLog(@"%@",[NSThread currentThread]); });}
全局并发队列:

全局并发队列是就是一个并发队列,是为了让我们更方便的使用多线程。dispatch_get_global_queue(0, 0)

//全局并发队列
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//全局并发队列的优先级
#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 // 后台优先级
//iOS8开始使用服务质量,现在获取全局并发队列时,可以直接传0
dispatch_get_global_queue(0, 0);
1. 串行队列(Serial Dispatch Queue)

串行队列的特点:
以先进先出的方式,按顺序调度队列中的任务去执行,一次只能调度一个任务。
无论队列中所指定的执行任务的函数是同步还是异步,都必须等待前一个任务执行完毕,才可以调度后面的任务。

串行队列的创建:

(1) dispatch_queue_tqueue = dispatch_queue_create("itheima", DISPATCH_QUEUE_SERIAL);

(2) dispatch_queue_tqueue = dispatch_queue_create("itheima", NULL);

串行队列,同步执行:
开不开线程? 不开线程。
顺序执行还是乱序执行? 顺序执行。

串行队列,异步执行:
开不开线程? 只会开一条线程。
顺序执行还是乱序执行? 顺序执行。

  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)所以只需要程序员决定创建什么类型的GCD队列和执行什么任务即可,其他由系统自行管理。
  • 任务的取出遵循FIFO模式:先进先出,后进后出
  • GCD定时器不受RunLoop约束,比NSTimer更加准时
  • 可操作可控性强,几乎没有GCD做不了的事情。
异步执行:
// dispatch_queue_t queue 队列// dispatch_block_t block 封装的代码段voiddispatch_async(dispatch_queue_t queue, dispatch_block_t block);

异步执行串行队列实例: 封装的代码一个一个放入到que的队列中去执行,因为是异步执行所以会再另开一条线程去执行,而又因为是串行队列,在一个任务执行完成前不会执行下一个所以只开1条线程,并且主线程继续执行.

图片 3异步执行串行队列.png

 dispatch_queue_t que = dispatch_queue_create("label", DISPATCH_QUEUE_SERIAL); for (int i = 0 ; i<10; i++) { dispatch_async(que, ^{ NSLog(@"%@",[NSThread currentThread]); });}

异步执行并发队列实例: 封装的代码一个一个放入到que的队列中去执行,因为是异步执行所以会再另开一条线程去执行,而又因为是并发队列,在一个任务执行完成前就会开一条新线程继续执行直到一个执行完,会开几条 新线程.

图片 4异步执行并发队列.png

dispatch_queue_t que = dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT);for (int i = 0 ; i<10; i++) { dispatch_async(que, ^{ NSLog(@"%@",[NSThread currentThread]); });}

主线程的同步死锁实例: 程序会卡主.主队列在等待 dispatch_sync调度完任务才会向下执行,而dispatch_sync也在等待主队列执行完才会执行

dispatch_queue_t mainque = dispatch_get_main_queue(); for (int i = 0 ; i<10; i++) { dispatch_sync(mainque, ^{ NSLog(@"%@",[NSThread currentThread]); }); } NSLog;

主线程的异步实例:先执行完主线程,在执行dispatch_async

dispatch_queue_t mainque = dispatch_get_main_queue(); for (int i = 0 ; i<10; i++) { dispatch_async(mainque, ^{ NSLog(@"%@",[NSThread currentThread]); }); }

解决同步死锁的方法:用异步的方式先开启子线程,在回到主线程同步执行

 dispatch_queue_t que = dispatch_queue_create("label", DISPATCH_QUEUE_CONCURRENT);dispatch_queue_t mainque = dispatch_get_main_queue();dispatch_async(que, ^{ NSLog(@"%@",[NSThread currentThread]); for (int i = 0 ; i<10; i++) { dispatch_sync(mainque, ^{ NSLog(@"%@",[NSThread currentThread]); }); }});

4. 同步/异步/任务、创建方式

  • 同步(sync)使用dispatch_sync来表示。
  • 异步(async)使用dispatch_async。
    任务就是将要在线程中执行的代码,将这段代码用block封装好。
    代码如下:
    // 同步执行任务
    dispatch_sync(dispatch_get_global_queue(0, 0), ^{
        // 任务放在这个block里
        NSLog(@"我是同步执行的任务");

    });
    // 异步执行任务
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 任务放在这个block里
        NSLog(@"我是异步执行的任务");

    });
2. 并发队列(Concurrent Dispatch Queue)
  • 并发队列的特点:以先进先出的方式,并发(同时)调度队列中的任务去执行。
  • 如果当前调度的任务是同步执行的,会等待当前任务执行完毕后,再调度后续的任务。
  • 如果当前调度的任务是异步执行的,同时底层线程池有可用的线程资源,就不会等待当前任务,直接调度任务到新线程去执行。

并发队列的创建:

dispatch_queue_t q = dispatch_queue_create("itheima", DISPATCH_QUEUE_CONCURRENT);

并发队列,同步执行:
开不开线程? 不开线程。
顺序执行还是乱序执行? 顺序执行。
并发同步 和 串行同步的执行结果一模一样。

并发队列,异步执行:
开不开线程? 开多条新线程。
顺序执行还是乱序执行? 乱序执行。

GCD的组成
队列(queue)+ 任务(block里执行的操作)

队列的组成
是否开启线程(同步,异步) + 任务执行的方式(主队列,串行队列,并发队列,全局并行队列)

延迟执行:

将一段代码延迟执行:

// dispatch_time_t when 要延迟多少时间// dispatch_queue_t queue 在哪个线程执行// dispatch_block_t block执行的代码段.voiddispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);

得出dispatch_time_t when延迟时间

// 一个固定计算的公式 返回值是秒数dispatch_time_tdispatch_time(dispatch_time_t when, int64_t delta);实例: // <#delayInSeconds#> 这里写秒数 如3.0.dispatch_time(DISPATCH_TIME_NOW, (<#delayInSeconds#> * NSEC_PER_SEC)

完整实例:

 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog; });

5. GCD的使用

由于有多种队列(串行/并发/主队列)和两种执行方式(同步/异步),所以他们之间可以有多种组合方式。

  1. 串行同步
  2. 串行异步
  3. 并发同步
  4. 并发异步
  5. 主队列同步
  6. 主队列异步
  • 串行同步
    执行完一个任务,再执行下一个任务。不开启新线程。
/** 串行同步 */
- (void)syncSerial {

    NSLog(@"nn**************串行同步***************nn");

    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    // 同步执行
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行同步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为顺序执行,都在主线程:

串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步1   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步2   {number = 1, name = main}
串行同步3   {number = 1, name = main}
串行同步3   {number = 1, name = main}
串行同步3   {number = 1, name = main}
  • 串行异步
    开启新线程,但因为任务是串行的,所以还是按顺序执行任务。
/** 串行异步 */
- (void)asyncSerial {

    NSLog(@"nn**************串行异步***************nn");

    // 串行队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_SERIAL);

    // 同步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行异步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"串行异步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为顺序执行

串行异步1   {number = 3, name = (null)}
串行异步1   {number = 3, name = (null)}
串行异步1   {number = 3, name = (null)}
串行异步2   {number = 3, name = (null)}
串行异步2   {number = 3, name = (null)}
串行异步2   {number = 3, name = (null)}
串行异步3   {number = 3, name = (null)}
串行异步3   {number = 3, name = (null)}
串行异步3   {number = 3, name = (null)}
  • 并发同步
    因为是同步的,所以执行完一个任务,再执行下一个任务。不会开启新线程
/** 并发同步 */
- (void)syncConcurrent {
    NSLog(@"nn**************并发同步***************nn");
    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);
    // 同步执行
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发同步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为顺序执行,都在主线程:

并发同步1   {number = 1, name = main}
并发同步1   {number = 1, name = main}
并发同步1   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步2   {number = 1, name = main}
并发同步3   {number = 1, name = main}
并发同步3   {number = 1, name = main}
并发同步3   {number = 1, name = main}
  • 并发异步
    任务交替执行,开启多线程。
/** 并发异步 */
- (void)asyncConcurrent {

    NSLog(@"nn**************并发异步***************nn");

    // 并发队列
    dispatch_queue_t queue = dispatch_queue_create("test", DISPATCH_QUEUE_CONCURRENT);

    // 同步执行
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发异步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"并发异步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为无序执行,有多条线程:

并发异步1   {number = 3, name = (null)}
并发异步2   {number = 4, name = (null)}
并发异步3   {number = 5, name = (null)}
并发异步1   {number = 3, name = (null)}
并发异步2   {number = 4, name = (null)}
并发异步3   {number = 5, name = (null)}
并发异步1   {number = 3, name = (null)}
并发异步2   {number = 4, name = (null)}
并发异步3   {number = 5, name = (null)}
  • 主队列同步
    如果在主线程中运用这种方式,则会发生死锁,程序崩溃。
/** 主队列同步 */
- (void)syncMain {

    NSLog(@"nn**************主队列同步,放到主线程会死锁***************nn");

    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列同步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列同步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列同步3   %@",[NSThread currentThread]);
        }
    });
}

主队列同步造成死锁的原因:

  1. 如果在主线程中运用主队列同步,也就是把任务放到了主线程的队列中。
  2. 而同步对于任务是立刻执行的,那么当把第一个任务放进主队列时,它就会立马执行。
  3. 可是主线程现在正在处理syncMain方法,任务需要等syncMain执行完才能执行。
  4. syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个和第三个任务。
  5. 这样syncMain方法和第一个任务就开始了互相等待,形成了死锁。
  • 主队列异步
    在主线程中任务按顺序执行。
/** 主队列异步 */
- (void)asyncMain {

    NSLog(@"nn**************主队列异步***************nn");

    // 主队列
    dispatch_queue_t queue = dispatch_get_main_queue();

    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列异步1   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列异步2   %@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 3; i++) {
            NSLog(@"主队列异步3   %@",[NSThread currentThread]);
        }
    });
}

输入结果为在主线程中按顺序执行:

主队列异步1   {number = 1, name = main}
主队列异步1   {number = 1, name = main}
主队列异步1   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步2   {number = 1, name = main}
主队列异步3   {number = 1, name = main}
主队列异步3   {number = 1, name = main}
主队列异步3   {number = 1, name = main}
3. 主队列:
  • 遇到主队列,不管同步异步都要先执行完主线程里的代码再执行主队列里的代码
  • dispatch_sync方法不能在主队列中调用,因为这会无限期的阻止线程并会导致你的应用死锁。所有通过GCD提交到主队列的任务必须是异步的。
  • 只有当主线程空闲时, 主队列才会调度任务到主线程执行
  • 主队列是系统提供的,无需自己创建,可以直接通过dispatch_get_main_queue()函数来获取。
  • 主队列的特点:先执行完主线程上的代码,才会执行主队列中的任务
    1、添加到主队列的任务只能由主线程来执行。
    2、以先进先出的方式,只有当主线程的代码执行完毕后,主队列才会调度任务到主线程执行。
    如下列代码执行打印123后再打印hello
- (void)demo1 {    
for (int i = 0; i < 10; i++) {       
NSLog(@"111");        
dispatch_async(dispatch_get_main_queue(), ^{            
NSLog(@"hello %d  %@",i,[NSThread currentThread]);
        });        
NSLog(@"222");        
NSLog(@"333");    
}
}
  • 主队列,同步执行(死锁)主线程和主队列同步任务相互等待,造成死锁。
    执行到同步主队列后,同步时主线程想让里面代码顺序执行下去(直接进去执行里面代码),而主队列要求执行完毕主线程代码再执行里面代码,导致谁都无法继续执行,导致死锁。
- (void)viewDidLoad {    
[super viewDidLoad];    
NSLog(@"begin");    
for (int i = 0; i < 10; i++) {        //死锁,一看同步就不分子线程了,一看主队列,就等着主线程执行完来执行里面代码       
dispatch_sync(dispatch_get_main_queue(), ^{            
NSLog(@"hello %d  %@",i,[NSThread currentThread]);        
});    }    
NSLog(@"end");
}
  • 解决死锁的问题,全局队列异步执行,里面嵌套主队列同步执行
    当我们将主队列同步执行任务放到子线程去执行,就不会出现死锁。
    由于将主队列同步放到了子线程中执行,主队列同步任务无法阻塞主线程执行代码,因此主线程可以将主线程上的代码执行完毕。当主线程执行完毕之后,就会执行主队列里面的任务。

先执行begin和end,再顺序执行全局队列异步执行(第二个线程里执行)

- (void)demo3 {
    NSLog(@"begin");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        for (int i = 0; i < 10; i++) {
            dispatch_sync(dispatch_get_main_queue(), ^{                
            NSLog(@"hello %d  %@",i,[NSThread currentThread]);            });        }
    });
    NSLog(@"end");
}
  • 主队列,异步执行
    执行到异步主队列后,里面代码异步执行后释放(不创建新线程),主线程执行完毕后回来调用主队列中代码

  • 开不开线程? 主队列,就算是异步执行,也不会开线程。

  • 顺序执行还是乱序执行? 顺序执行。

  • 先把主线程上的代码执行完毕,才会执行添加到主队列里面的任务。

  • 主队列和串行队列的区别

  • 串行队列:必须等待一个任务执行完毕,才会调度下一个任务,顺序执行代码。

  • 主队列:如果主线程上有代码执行,主队列就不调度任务,跳过主队列代码执行主线程代码,完毕后再执行主队列内代码。

  • 同步(dispatch_sync):只能在当前线程执行任务,不具备开启新线程能力。
  • 异步(dispatch_async):可以在新线程执行任务,具备开启新线程能力。

整个程序只执行一次:

只调用一次:

// dispatch_once_t *predicate 一个标记 是一个long类型传入的是这个类型的地址&// dispatch_block_t block 这个代码段在程序中,只调用一次._dispatch_once(dispatch_once_t *predicate, dispatch_block_t block)

实例:

static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSLog; });

GCD线程之间的通讯

开发中需要在主线程上进行UI的相关操作,通常会把一些耗时的操作放在其他线程,比如说图片文件下载等耗时操作。

当完成了耗时操作之后,需要回到主线程进行UI的处理,这里就用到了线程之间的通讯。

- (IBAction)communicationBetweenThread:(id)sender {

    // 异步
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 耗时操作放在这里,例如下载图片。(运用线程休眠两秒来模拟耗时操作)
        [NSThread sleepForTimeInterval:2];
        NSString *picURLStr = @"http://www.bangmangxuan.net/uploads/allimg/160320/74-160320130500.jpg";
        NSURL *picURL = [NSURL URLWithString:picURLStr];
        NSData *picData = [NSData dataWithContentsOfURL:picURL];
        UIImage *image = [UIImage imageWithData:picData];

        // 回到主线程处理UI
        dispatch_async(dispatch_get_main_queue(), ^{
            // 在主线程上添加图片
            self.imageView.image = image;
        });
    });
}

上面的代码是在新开的线程中进行图片的下载,下载完成之后回到主线程显示图片。

4. 全局队列:
  • 全局队列是系统提供的,无需自己创建,可以直接通过dispatch_get_global_queue(long identifier, unsigned long flags);函数来获取。

  • 全局队列的工作特性跟并发队列一致。 实际上,全局队列就是系统为了方便程序员,专门提供的一种特殊的并发队列。

  • 全局队列和并发队列的区别

  • 全局队列:没有名称,无论ARC还是MRC都不需要考虑内存释放,日常开发,建议使用全局队列

  • 并发队列:
    (1)有名称,如果在MRC开发中,需要使用dispatch_release来释放相应的对象
    (2)dispatch_barrier 必须使用自定义的并发队列
    (3)开发第三方框架,建议使用并发队列

  • 第一个参数: identifier
    iOS7.0,表示的是优先级:

    DISPATCH_QUEUE_PRIORITY_HIGH = 2; 高优先级
    DISPATCH_QUEUE_PRIORITY_DEFAULT = 0; 默认优先级
    DISPATCH_QUEUE_PRIORITY_LOW = -2; 低优先级
    DISPATCH_QUEUE_PRIORITY_BACKGROUND = INT16_MIN; 后台优先级

iOS8.0开始,推荐使用服务质量(QOS):

    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; 未指定

通过对比可知: 第一个参数传入0,可以同时适配iOS7及iOS7以后的版本。
服务质量和优先级是一一对应的:

    DISPATCH_QUEUE_PRIORITY_HIGH:          QOS_CLASS_USER_INITIATED
    DISPATCH_QUEUE_PRIORITY_DEFAULT:      QOS_CLASS_DEFAULT
    DISPATCH_QUEUE_PRIORITY_LOW:           QOS_CLASS_UTILITY
    DISPATCH_QUEUE_PRIORITY_BACKGROUND:   QOS_CLASS_BACKGROUND
  • 第二个参数: flags 为未来保留使用的,始终传入0。

本文由胜博发-编程发布,转载请注明来源:同步执行每个任务不完成的时候不会执行下一个