同步、异步、并发、串行

同步和异步主要影响:能不能开启新的线程

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

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

并发和串行主要影响:任务的执行方法

并发:在当前的线程中执行任务,不具备开启新线程的能力

串行:一个任务执行完毕后,在执行下一个任务

正文的开始请先看一个例子 下面的异步执行是在主线程还是子线程?

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t quene = dispatch_get_main_queue();

    dispatch_async(quene, ^{

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

    });

}

打印结果 :nsthread:{number = 1, name = main};

官方文档 对 dispatch_async 的解释

This function is the fundamental mechanism for submitting blocks to a dispatch queue. Calls to this function always return immediately after the block is 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

大致翻译

这个函数是将块提交到调度队列的基本机制。对该函数的调用总是在块提交后立即返回,并且从不等待块被调用。目标队列确定相对于提交到同一队列的其他块是串行调用还是并发调用该块。独立的串行队列相互并发处理

依然是在主线程执行 因为是在主队列执行这个异步操作 可能会有人不明白 带着这个问题我们接着往下看

先看一道经典的面试题 下面的代码会打印哪些 为什么?

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"1");

    dispatch_queue_t quene = dispatch_get_main_queue();

    dispatch_sync(quene, ^{

        NSLog(@"2");

    });

    NSLog(@"3");

}

打印:1

首先我们先看下官方文档

dispatch_queue_t

A dispatch queue that executes blocks serially in FIFO order.

dispath_sync

This function submits a block to the specified 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.

Unlike with dispatch_async, no retain is performed on the target queue. Because calls to this function are synchronous, it borrows the reference of the caller. Moreover, no Block_copy is performed on the block. 

As a performance optimization, this function executes blocks on the current thread whenever possible, with one exception: Blocks submitted to the main dispatch queue always run on the main thread.

大致的翻译如下

该函数将块提交到指定的调度队列以同步执行。与 dispatch_async 不同,此函数在块完成之前不会返回。调用此函数并以当前队列为目标会导致死锁。

与 dispatch_async 不同,在目标队列上不执行任何保留。因为对这个函数的调用是同步的,所以它“借用”了调用者的引用。此外,没有对块执行 Block_copy。

作为性能优化,该函数尽可能在当前线程上执行块,只有一个例外:提交到主调度队列的块总是在主线程上运行。

quene 队列

特点: FIFO First In First Out 先进先出

这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务

dispath_sync 主线程

特点 立马执行当前的线程任务 执行完毕才能继续往下执行

所以要执行这个队列 必须等 viewdidload 执行完毕 (这里是要执行到 NSlog(@执行任务3))才能 走这个quene的队列任务) 而dispath_sync 又必须立马执行 所以造成了 NSlog(@2)与 NSlog(@3) 互相等待执行 最后就造成了死锁

60e674c91c382.png

然后我们把同步换到异步执行

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"1");

    dispatch_queue_t quene = dispatch_get_main_queue();

    dispatch_async(quene, ^{

        NSLog(@"2");

    });

    NSLog(@"3");

}

主线程里面进行执行异步操作 结果可想而知

打印:1,3,2

然后我们把上面的问题升级一下

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"1");

    dispatch_queue_t quene = dispatch_queue_create(quene, DISPATCH_QUEUE_SERIAL);

    dispatch_async(quene, ^{

        NSLog(@"2");

        dispatch_sync(quene, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

    NSLog(@"5");

}```

我们先看下打印结果

打印:1,5,2

1,5执行 不奇怪

这时的quene是串行队列也就是必须等第一个quene执行完成才能执行完成下一个 理论是 2,4,3 但是dispath_sync走到这里又必须立马执行 所以问题产生跟上面的问题一样 所以就造成任务3跟任务4 互相等待执行 造成死锁

继续升级这个问题

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"1");

    dispatch_queue_t quene = dispatch_queue_create("quene", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t quene1 = dispatch_queue_create("quene1", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(quene, ^{

        NSLog(@"2");

        dispatch_sync(quene1, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

   NSLog(@"5");

}

打印:1,5,2,3,4

这个问题是新开了一个并行队列 导致会把quene队列的任务执行完 又因为dispath_sync 是同步 所以会依次执行完成再往下面执行

所以总结一下上面的问题 因为大伙都处于同一个队列 然后结合队列的特点 所以就造成互相等待的情况 进而引发后面的问题

再来改进一下 请思考一分钟给出结果

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"1");

    dispatch_queue_t quene = dispatch_queue_create("quene", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t quene1 = dispatch_queue_create("quene1", DISPATCH_QUEUE_SERIAL);

    dispatch_async(quene, ^{

        NSLog(@"2");

        dispatch_sync(quene1, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

    NSLog(@"5");

}

答案同上

接二连三的改进下问题

- (void)viewDidLoad {

    [super viewDidLoad];

    NSLog(@"1");

    dispatch_queue_t quene = dispatch_queue_create("quene", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(quene, ^{

        NSLog(@"2");

        dispatch_sync(quene, ^{

            NSLog(@"3");

        });

        NSLog(@"4");

    });

    NSLog(@"5");

}

答案依旧同上

最后我们用一句话来总结今天的例子

使用Sync函数往当前串行队列中添加任务,会卡死当前的串行队列(产生死锁)

同时满足同步和当前串行队列 才会产生死锁 往其他队列添加也不会造成死锁 如果还有不明白的 请把这句结论拿到上面的例子中套一下 看是不是就豁然开朗了

最后的最后 我们再来额外的补充一点知识

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t quene1 = dispatch_get_global_queue(0, 0);

    dispatch_queue_t quene2 = dispatch_get_global_queue(0, 0);

    dispatch_queue_t quene3 = dispatch_queue_create("quene3", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t quene4 = dispatch_queue_create("quene4", DISPATCH_QUEUE_SERIAL);

    dispatch_queue_t quene5 = dispatch_queue_create("quene5", DISPATCH_QUEUE_CONCURRENT);

      NSLog(@"%p,%p,%p,%p,%p",quene1,quene2,quene3,quene4,quene5);

}

打印结果:0x10bf1a080,0x10bf1a080,0x600002a5d800,0x600002a5cf80,0x600002a5d880

我想说明的有两个点 第一个是 dispatch_get_global_queue 是系统帮我们封装好的获取全局的队列 可以看作是系统为我们开启的一个新的全局并行队列 既然是全局的 所以前面两个打印的地址是一样的 第二个是由我们自己创建的队列 就算是名字是一样的 但是创建的队列地址也是不一样的 所以名字是否相同不会影响的子队列的创建 但是 反过来你想通过名称去获取到这个队列 就会出现问题

看一个面试题

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t quene = dispatch_get_global_queue(0, 0);

    dispatch_async(quene, ^{

        NSLog(@"1");

        [self performSelector:@selector(test) withObject:nil afterDelay:1];

        NSLog(@"2");

    });

}

- (void)test{

    NSLog(@"3");

}

打印结果 :1,2

请参考我的文章 内存详解 这篇文章的后面也提到了 主线程是默认开启runloop的 子线程有开启线程的能力 但是默认不开启 需要用户手动开启

performSelector方法本质就是往runloop加入定时器 所以手动开启runloop之后

- (void)viewDidLoad {

    [super viewDidLoad];

    dispatch_queue_t quene = dispatch_get_global_queue(0, 0);

    dispatch_async(quene, ^{

        NSLog(@"1");

        [self performSelector:@selector(test) withObject:nil afterDelay:1];

        [[NSRunLoop currentRunLoop] run];

        NSLog(@"2");

    });

}

- (void)test{

    NSLog(@"3");

}

在子线程开启NSTimer 需要开启runloop 否则不会执行

6214b436cef98.png

用一个表格来结束今天的文章0_0

并发队列

串行队列

主队列

同步(Sync)

没有开启新线程 串行执行任务

没有开启新线程 串行执行任务 | 没有开启新线程 串行执行任务

异步(Async)

有开启新线程 并发执行任务

有开启新线程 串行执行任务 | 没有开启新线程 串行执行任务