GCD的学习笔记(One)

  • 并行和并发
  • GCD简介
  • GCD的任务
  • GCD的队列
  • GCD创建队列或获取队列的方法
  • 任务的执行方式:同步执行(同步)和异步执行(异步)
  • GCD提交执行任务的具体方法
  • 1.异步执行一个并发队列
  • 2.异步执行一个串行队列(非主队列)
  • 3.同步执行串行队列(非主队列)
  • 4.同步执行并发队列
  • 5.异步执行主队列(在主线程中)
  • 6.同步执行主队列(在主线程中)

本文参考文章链接:


前言

  • 这是GCD系列的第一篇,写的基础东西比较多,所以文章较长,可以按照目录分段来看。

  • 可能会存在理解不到位的情况及书写纰漏,烦请指出,谢谢。

  • 对你有帮助的话,请点个喜欢或留言,谢谢。>_<


GCD1之综合理解


并行和并发

并发表示同时发生了多件事情,通过时间片切换,哪怕只有单一的核心,也可以实现“同时做多件事情”这个效果。根据底层是否有多处理器,并发与并行是可以等效的,这并不是两个互斥的概念。举个我们开发中会遇到的例子,我们说资源请求并发数达到了1万。这里的意思是有1万个请求同时过来了。但是这里很明显不可能真正的同时去处理这1万个请求的吧!如果这台机器的处理器有4个核心,不考虑超线程,那么我们认为同时会有4个线程在跑。也就是说,并发访问数是1万,而底层真实的并行处理的请求数是4。如果并发数小一些只有4的话,又或者你的机器牛逼有1万个核心,那并发在这里和并行一个效果。也就是说,并发可以是虚拟的同时执行,也可以是真的同时执行。而并行的意思是真的同时执行。结论是:并行是我们物理时空观下的同时执行,而并发则是操作系统用线程这个模型抽象之后站在线程的视角上看到的“同时”执行

并发是逻辑上的同时发生,并行更多是侧重于物理上的同时发生。并发往往是指程序代码的结构支持并发,并发的程序在多cpu上运行起来才有可能达到并行,并行往往是描述运行时的状态


GCD简介

  1. GCD全称Grand Central Dispatch是苹果提供的一个多核编程的解决方案,在真正意义上实现了并行操作,而不是并发GCD使用线程池模型来执行用户提交的任务,所以它比较节约资源,不需要为每个任务都重新创建一个新的线程,GCD不需要自行编写并行代码,而是自动进行多核的并行计算,自动管理线程的生命周期,如:使用线程池管理线程的创建和销毁,线程的调度,任务的调度等,用户只需要编写任务代码并提交即可

  2. GCD(Grand Central Dispatch)伟大的中央调度系统,是苹果为多核并行运算提出的C语言并发技术框架。

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

  • queue:队列
    用来存放任务的先进先出(FIFO)的容器

  • sync:同步
    只是在当前线程中执行任务,不具备开启新线程的能力

  • async:异步
    可以在新的线程中执行任务,具备开启新线程的能力

  • concurrent:并发
    多个任务并发(同时)执行

  • serial:串行
    一个任务执行完毕后,再执行下一个任务

  1. GCD中有两个比较重要的概念:任务和队列

GCD的任务

任务顾名思义就是我们需要执行的代码块,可以是一个方法也可以是一个block,就是我们需要线程为我们完成的工作,编写完成的任务只需提交给GCD的队列,即可自动帮我们完成任务的调度,以及线程的调度,可以很方便的以多线程的方式执行。


GCD的队列

队列用于管理用户提交的任务,GCD的队列有两种形式,串行队列和并发队列:

  • 串行队列: GCD底层只维护一个线程,任务只能串行依次执行。
  • 并发队列: GCD底层使用线程池维护多个线程,任务可并发执行

不论是串行队列还是并发队列都使用 先进先出(FIFO,First Input First Output) 的方式来管理用户提交的任务。

  • 对于串行队列来说,GCD每次从串行队列的队首取一个任务交给唯一的一个线程来处理,直到前一个任务完成后,才继续从队列中取下一个任务来执行,因此,串行队列中的任务执行严格按照提交顺序,并且后一个任务必须等前一个任务执行完成后才可以执行

  • 对于并发队列来说,GCD每次从并发队列的队首取一个任务,并将这个任务按照任务调度分发给多个线程中的某一个线程,此时不需要等待其完成,如果队列中还有其他任务,继续从队列中取出并分发给某一个线程来执行,由于底层由线程池管理多个线程,每个任务的时间复杂度不同再加上线程调度的影响,后提交的任务可能先执行完成。但对于单个线程执行并发队列来说,只能按队列里的任务顺序顺序执行,比如某个线程被安排了多个任务,那这个线程就只能按提交顺序依次执行任务

所以,我们在使用GCD时也就很简单了,只需要创建或获取系统队列、编写任务并提交任务到队列即可

注意点:

  1. 线程和队列的关系:线程是来执行队列里的任务的。一个线程可以执行多个队列,也就是说,一个线程可以在多个队列里来回切换着执行,执行完这个队列里的一个任务,再执行另一个队列里的任务。

  2. 主队列:主队列是一个串行队列,只有主线程能够执行主队列里的任务。平常的程序,都是放进主队列里来执行的。

  3. 串行队列每次是由一个线程来执行一个任务的。 但是注意,只是一个线程而已,不代表这个线程一直会是同一个。如果这个一个线程执行完一个任务后被销毁了,会由另一个线程继续执行这个串行队列。

  4. The global concurrent queues invoke blocks in FIFO order but do not wait for their completion, allowing multiple blocks to be invoked concurrently. 全局并发队列以FIFO顺序调用块,但不等待它们完成,允许同时调用多个块。

  5. 并发队列的任务分发的时候,如果有多个线程,可以分给多个线程。但是,也可以将多个任务同时分给一个线程。这个看GCD拾遗2。


GCD创建队列或获取队列的方法

  1. 获取主队列,即与主线程相关联的队列,它为串行队列:
/*
获取主队列,即与主线程相关联的队列
如果需要提交任务到主线程使用该方法获取主线程的主队列即可
主队列是串行队列因为只维护主线程一个线程
*/
dispatch_queue_t dispatch_get_main_queue(void);
  1. 获取一个全局的并发队列
/*
获取一个全局的并发队列
identifier指定该队列的优先级可选值有:
    DISPATCH_QUEUE_PRIORITY_HIGH 2
    DISPATCH_QUEUE_PRIORITY_DEFAULT 0
    DISPATCH_QUEUE_PRIORITY_LOW (-2)
    DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN
flags:苹果关于第二个参数flags是这样写的:Flags that are reserved for future use. Always specify 0 for this parameter.保留供将来使用的标志。始终为此参数指定0。也就是说,flags一定要写0。有点疑惑这里??
*/
dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);
  1. 创建一个队列。注意这里的第一个参数为const char *,为一个常量字符指针
/*
创建一个队列
label 队列的名称
attr 队列的属性可选值有:
    DISPATCH_QUEUE_SERIAL 创建一个串行队列
    DISPATCH_QUEUE_CONCURRENT 创建一个并发队列
通过这种方式可以自己维护一个队列
*/
dispatch_queue_t dispatch_queue_create(const char *_Nullable label, dispatch_queue_attr_t _Nullable attr);
  1. 具体的使用。注意第三个,传的是"myConcurrentQueue",不是字符串常量
//获取串行主队列
dispatch_queue_t mainQueue = dispatch_get_mian_queue();

//获取一个默认优先级的并发队列
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT);

//自定义创建一个名称为myConcurrentQueue的并发队列
dispatch_queue_t myConcurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);

任务的执行方式:同步执行(同步)和异步执行(异步)

队列创建完成以后就可以编写任务并提交了,接下来介绍两种执行方式,同步执行和异步执行。

1. 异步执行,dispatch_async

我们从看GCD常用的一个异步执行函数来看,此函数是用来异步执行一个block的。queue(第一个参数)是队列,并发队列或串行队列;block(第二个参数)里面装着你的任务。

void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  1. 苹果的官方文档里面是这样写的:

Submits a block for asynchronous execution on a dispatch queue and returns immediately.

This function is the fundamental mechanism for submitting blocks to a dispatch queue. Calls to this function always return immediately after the block has been submitted and never wait for the block to be invoked. The target queue determines whether the block is invoked serially or concurrently with respect to other blocks submitted to that same queue. Independent serial queues are processed concurrently with respect to each other.

翻译如下:

使用 异步执行 提交一个块(block)到调度队列上并立即返回。(块,block,里面装着你要执行的任务)

此函数是将块(block)提交到调度队列的基本机制。 调用此函数后,始终在提交块后立即返回,并且永远不会等待调用该块。 目标队列来确定/是否相对于/提交到同一队列的其他块/串行或并发调用该块。 独立的串行队列相互同时处理。

  1. 解释

异步执行,dispatch_async:异步就是在调用了dispatch_async这个函数后立即返回,不去管里面的任务到底是怎么样执行的。

  1. 注意点

异步具有开启多线程的能力,但是你要明白,异步可以开辟一条新的线程执行队列的任务,也可以不开辟新的线程执行队列的任务,这个交给系统来决定。

2. 同步执行,dispatch_sync

我们从看GCD常用的一个同步执行函数来看,此函数是用来异步执行一个block的。queue(第一个参数)是队列,并发队列或串行队列;block(第二个参数)里面装着你的任务。

void dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
  1. 苹果的官方文档里面关于是这样写的:

Submits a block object for execution on a dispatch queue and waits until that block completes.

Submits a block to a dispatch queue for synchronous execution. Unlike dispatch_async, this function does not return until the block has finished. Calling this function and targeting the current queue results in deadlock.

翻译如下:

提交块对象以在调度队列上执行,并等待该块完成。

将块提交到调度队列以进行同步执行。 与dispatch_async不同,此功能在块完成之前不会返回。 调用此函数并以当前队列为目标会导致死锁(这里队列应该是指串行队列,个人理解)。

  1. 解释

同步执行,dispatch_sync:同步就是将任务提交给队列后,必须等待这个任务完成才能继续往下走。注意死锁的解释(调用此函数并以当前队列为目标会导致死锁,这里队列应该是指串行队列,个人理解),下面会讲。

主要还是第一句话,同步执行会等待里面的任务完成了,才能继续往下走。

  1. 注意

同步不具备开启新线程的能力,它在哪个线程里被调用,就使用哪个线程执行任务。但是有一个特殊情况,如果你使用的是主队列(dispatch_get_main_queue()),那么会使用主线程来执行任务,因为主队列里的任务必须使用主线程来执行。


GCD提交执行任务的具体方法

  1. async,以异步方式执行任务,任务为block形式,不阻塞当前线程
/*
以异步方式执行任务,不阻塞当前线程
queue 管理任务的队列,任务最终交由该队列来执行
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
  1. async_f,以异步方式执行任务(使用起来不方便,不怎么用),任务为dispatch_function_t形式
/*
同上
context 是一个void*的指针,是work的第一个形参
work 是一个函数指针,指向返回值为void、形参为void*的函数,且形参不能为NULL,也就是说context一定要传,
使用起来不方便,一般不怎么用,需要使用C函数,也可以使用OC方法通过传递IMP来执行但是会有编译警告
*/
void dispatch_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

  1. sync,以同步方式执行任务,任务为block形式,阻塞当前线程
/*
以同步方式执行任务,阻塞当前线程,必须等待任务完成当前线程才可继续执行
*/
void dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
  1. sync_f,同上(使用起来不方便,不怎么用)
//同上
void dispatch_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

  1. after,以异步方式提交任务,在when时间点提交任务。比1多了一个时间点。
/*
以异步方式提交任务,在when时间点提交任务
queue 管理任务的队列,任务最终交由该队列来执行
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
  1. after_f,同上(使用起来不方便,不怎么用)
//同上
void dispatch_after_f(dispatch_time_t when, dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
  1. barrier_async,以异步方式提交任务,会阻塞queue队列,但不阻塞当前线程,需要说明的是,即使使用并发队列,该队列也会被阻塞,前一个任务执行完成才能执行下一个任务。也就是说,这种方式下,队列都会被当成串行队列处理
/*
以异步方式提交任务,会阻塞queue队列,但不阻塞当前线程
queue 管理任务的队列,任务最终交由该队列来执行
需要说明的是,即时使用并发队列,该队列也会被阻塞,前一个任务执行完成才能执行下一个任务
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);
  1. async_f,同上(使用起来不方便,不怎么用)
//同上
void dispatch_barrier_async_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);

  1. barrier_sync,以同步方式提交任务,会阻塞queue队列,也会阻塞当前线程,同样的,即时是并发队列该队列也会被阻塞,需要等待前一个任务完成,同时线程也会阻塞。也就是说,这种方式下,队列都会被当成串行队列处理
/*
以同步方式提交任务,会阻塞queue队列,也会阻塞当前线程
queue 管理任务的队列,任务最终交由该队列来执行
同样的,即时是并发队列该队列也会被阻塞,需要等待前一个任务完成,同时线程也会阻塞
block block形式的任务,该block返回值、形参都为void
*/
void dispatch_barrier_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
  1. sync_f,同上(使用起来不方便,不怎么用)
/同上
void dispatch_barrier_sync_f(dispatch_queue_t queue, void *_Nullable context, dispatch_function_t work);
  1. once,底层线程池控制block任务在整个应用的生命周期内只执行一次。该方法常用于实现单例类,以及结合RunLoop创建一个常驻内存的线程
/*
底层线程池控制block任务在整个应用的生命周期内只执行一次
predicate 实际为long类型,用于判断是否执行过
block block形式的任务,该block返回值、形参都为void
该方法常用于实现单例类,以及结合RunLoop创建一个常驻内存的线程
*/
void dispatch_once(dispatch_once_t *predicate, dispatch_block_t block);

异步、同步和串行、并行,两两结合有四种情况;在异步主队列、同步主队列;下面共列出了六种。

注意,输出的number = 1为主线程,number != 1 为子线程

1. 异步执行一个并发队列

- (void)viewWillAppear:(BOOL)animated
{
    //手动创建了一个并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    //也可以获取全局的并发队列,效果一样
    //dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //异步提交一个任务到并发队列
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    });
    //异步提交一个任务到并发队列
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
        }
    });
    //异步提交一个任务到并发队列
    dispatch_async(concurrentQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
        }
    });

    //使用传递函数指针的方式有点复杂,以后的栗子不再赘述
    int context = 0;
    dispatch_async_f(concurrentQueue, &context, cFuncTask);
    //也可以使用OC方法,传入IMP,但会有警告
    //dispatch_async_f(concurrentQueue, &context, [self methodForSelector:@selector(ocFuncTask:)]);
}

//该函数是C函数
void cFuncTask(void* context)
{
    for (int i = 0; i < 500; i++)
    {
        NSLog(@"Task4 %@ %d", [NSThread currentThread], i);
    }
}
//OC方法
- (void)ocFuncTask:(void*) context
{
    for (int i = 0; i < 500; i++)
    {
        NSLog(@"Task4 %@ %d", [NSThread currentThread], i);
    }
}

输出结果:Task1-4乱序输出,且每个任务都是由不同的线程执行的。所以通过异步提交任务到一个并发队列是真正实现了并发执行

摘取四个输出结果如下:

Task4 <NSThread: 0x1c04776c0>{number = 7, name = (null)} 88
Task3 <NSThread: 0x1c04770c0>{number = 6, name = (null)} 63
Task1 <NSThread: 0x1c0474980>{number = 4, name = (null)} 99
Task2 <NSThread: 0x1c427c5c0>{number = 5, name = (null)} 0

解释:异步:具备开启多线程的能力;并发:可以将队列里的任务按照先进先出的顺序,分派给多个线程来执行。所以这里,系统就开启了4条子线程来执行并发队列里的4个任务。


2. 异步执行一个串行队列(非主队列)

- (void)viewWillAppear:(BOOL)animated
{
    //创建一个串行队列
    dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);

    //异步提交一个任务到串行队列
    dispatch_async(serialQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    });
    //异步提交一个任务到串行队列
    dispatch_async(serialQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
        }
    });
    //异步提交一个任务到串行队列
    dispatch_async(serialQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
        }
    });
}

输出结果:Task1-3顺序输出,也就是说后一个任务只能等到前一个任务完成了才能执行,但是执行任务的线程都是同一个。

摘取三个输出结果如下:

Task1 <NSThread: 0x1c0462280>{number = 4, name = (null)} 0
Task2 <NSThread: 0x1c0462280>{number = 4, name = (null)} 0
Task3 <NSThread: 0x1c0462280>{number = 4, name = (null)} 0

解释:异步,具备开启多线程的能力;串行,只能一个一个的执行,且串行队列只能由同一个线程来执行。这里的number = 4,表示是子线程。所以异步是开启了另一条线程来执行这个串行队列。理论上,也有可能交给主线程来执行这个串行队列,那么number = 1。


3. 同步执行串行队列(非主队列)

- (void)viewWillAppear:(BOOL)animated
{
    /*
    创建一个串行队列
    这里不可以使用主队列,因为执行该方法的是主线程,如果使用同步执行提交到主队列会造成死锁,后文会有具体讲解
    */
    dispatch_queue_t serialQueue = dispatch_queue_create("mySerialQueue", DISPATCH_QUEUE_SERIAL);

    //同步提交一个任务到串行队列
    dispatch_sync(serialQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    });

    //同步提交一个任务到串行队列
    dispatch_sync(serialQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
        }
    });

    //同步提交一个任务到串行队列
    dispatch_sync(serialQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
        }
    });
}

输出结果:还是按照Task1-3顺序输出,也就是后一个任务必须在前一个任务完成后才能执行。

Task1 <NSThread: 0x1c0072c80>{number = 1, name = main} 0
Task2 <NSThread: 0x1c0072c80>{number = 1, name = main} 0
Task3 <NSThread: 0x1c0072c80>{number = 1, name = main} 0

解释:同步:线程执行完当前任务后才能继续往下走,只能使用当前线程(使用主队列的情况另说),不具备开启新线程的能力。串行:一个一个执行任务,且由同一个线程来执行。因为viewWillAppear:是在主线程中,同步执行使用当前线程,所以number=1,且按任务顺序输出。


4. 同步执行并发队列

- (void)viewWillAppear:(BOOL)animated
{
    //手动创建了一个并发队列
    dispatch_queue_t concurrentQueue = dispatch_queue_create("myConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    //也可以获取全局的并发队列,效果一样
    //dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    //同步提交一个任务到并发队列
    dispatch_sync(concurrentQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    });

    //同步提交一个任务到并发队列
    dispatch_sync(concurrentQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
        }
    });

    //同步提交一个任务到并发队列
    dispatch_sync(concurrentQueue, ^{
        for (int i = 0; i < 500; i++)
        {
            NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
        }
    });
}

输出结果:按照Task1-3顺序输出。

Task1 <NSThread: 0x1c0263200>{number = 1, name = main} 0
Task2 <NSThread: 0x1c0263200>{number = 1, name = main} 0
Task3 <NSThread: 0x1c0263200>{number = 1, name = main} 0

解释:同步,线程执行完当前任务后才能继续往下走,使用当前线程来执行,不具备开启新线程的能力。并发队列:可以同时将队列里的任务按照先进先出的顺序交给多个线程来执行。因为是同步执行,所以只能使用当前线程(这里是主线程,number=1)来执行任务,且同步执行必须执行完一个任务才能继续,所以并发队列里的任务只能乖乖的按照顺序交给当前线程,一个一个执行。

5. 异步执行主队列(在主线程中)

主队列是一个串行队列,且只能由主线程来执行。这里你还要明白一点,主线程也是可以执行其他队列的,只是主队列必须交给主线程去执行。平常的程序,不使用多线程的情况下,都会放在主队列中。

- (void)viewDidLoad {
    [super viewDidLoad];
    
    //主队列是串行队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue();

    //异步提交一个任务到主队列
    dispatch_async(mainQueue, ^{
        for (int i = 0; i < 50; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    });
    //异步提交一个任务到主队列
    dispatch_async(mainQueue, ^{
        for (int i = 0; i < 50; i++)
        {
            NSLog(@"Task2 %@ %d", [NSThread currentThread], i);
        }
    });
    //异步提交一个任务到主队列
    dispatch_async(mainQueue, ^{
        for (int i = 0; i < 50; i++)
        {
            NSLog(@"Task3 %@ %d", [NSThread currentThread], i);
        }
    });
    
    //打印一行xxxxxxxxxx
    NSLog(@"xxxxxxxxxxxxxxx");
}

输出结果:先输出了xxxxxxxxxxx,再按顺序打印

2018-08-17 11:00:07.697525+0800 GCD[859:68323] xxxxxxxxxxxxxxx
2018-08-17 11:00:07.702203+0800 GCD[859:68323] Task1 <NSThread: 0x60000006d3c0>{number = 1, name = main} 0
2018-08-17 11:00:07.702550+0800 GCD[859:68323] Task1 <NSThread: 0x60000006d3c0>{number = 1, name = main} 1
2018-08-17 11:00:07.703024+0800 GCD[859:68323] Task1 <NSThread: 0x60000006d3c0>{number = 1, name = main} 2
.....后面按照Task1-3的顺序输出

解释:

  1. 为什么使用了主线程来执行了Task1-3?因为主队列只能交给主线程来执行,即使你是异步也不行。

  2. 为什么先输出了xxxxxxxxxx,再输出了Task1-3?我们可以将viewDidLoad(){}看作是一个任务,这个任务是在主队列中的,它现在排在主队列的队首位置。异步执行的意思是:马上返回,不必等待任务执行完毕,我不管你怎么执行任务。也就是说,当主线程在执行主队列的viewDidLoad(){}这个任务时,任务里面碰到了三个异步执行,但是异步执行都马上返回了,所以可以继续往下走,就输出了xxxxxxxxx。而这三个异步执行,将任务提交给了主队列,主队列的队首任务是viewDidLoad(){},所以Task1-3要排在它的后面。当主队列的队首任务viewDidLoad(){}执行完后,就可以执行Task1-3了。

6. 同步执行主队列(在主线程中)

在主线程中,同步执行主队列,会造成死锁。上面苹果文档那句话,在同步执行里,调用此函数并以当前队列为目标会导致死锁。但根据试验和分析,只有同步执行的串行队列的情况下会造成死锁****。

分析:在主线程,当前队列为主队列,然后使用同步执行提交任务到主队列,死锁。在第二篇中《GCD2之死锁》有详尽分析。

- (void)viewDidLoad {
    [super viewDidLoad];

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

    NSLog(@"开始");
    
    //异步提交一个任务到串行队列
    dispatch_sync(mainQueue, ^{
        for (int i = 0; i < 50; i++)
        {
            NSLog(@"Task1 %@ %d", [NSThread currentThread], i);
        }
    });
    
    NSLog(@"结束");
}

输出:

2018-08-17 11:21:04.826284+0800 GCD[882:81994] 开始

然后就崩溃了

注意:
记住一点,只有同步执行加串行队列会造成死锁。请看第二篇《GCD2之死锁》,妈呀,这题目有点恐怖故事的感觉,小心哟,嘿嘿嘿...

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,332评论 5 475
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,930评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,204评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,348评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,356评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,447评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,862评论 3 394
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,516评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,710评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,518评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,582评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,295评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,848评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,881评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,121评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,737评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,280评论 2 341

推荐阅读更多精彩内容