iOS基础:多线程-深入理解GCD

前言

本篇文章不会介绍GCD的底层实现!!!
本篇文章不会介绍GCD的基本使用!!!
本篇文章是我这两天再次理解GCD后的记录。说实话,虽然在开发时知道多线程是什么、如何用,但是对于概念真的有点模棱两可,而线程死锁之类的问题更是一知半解。

正文

一、GCD的官方定义

开发者需要做的只是定义想执行的任务并将其追加到适当的Dispatch Queue中。

二、Dispatch Queue

下面先来说说定义中的Dispatch Queue

dispatch_async(queue, ^{
    //block
});

以上代码的意义是:用dispatch_async这样的函数将block追加到Dispatch Queue中。而block内容便是想执行的任务。而Dispatch Queue,指的就是执行处理的等待队列。
Dispatch Queue有着先进先出的原则。他会把你先放进去的任务先拿出来执行。
Dispatch Queue有两种,分别为Serial Dispatch Queue 和 Concurrent Dispatch Queue。

iOS基础:多线程-深入理解GCD_第1张图片
Dispatch Queue1.png

就像图片所示,Serial Dispatch Queue也就是串行队列,他会一个个把任务拿出来执行,并且在拿下一个任务的时候,上一个任务必须已经是执行完毕了。
而Concurrent Dispatch Queue也就是并行队列,他会把任务按照顺序快速的拿出来,因为他不需要等待上一个任务的执行完毕。

三、同步&&异步

同步:阻塞当前线程,必须要等待同步线程中的任务执行完,返回以后,才能继续执行下一任务。
异步:不会阻塞当前线程,会开启新的线程而不需要等待当前线程的任务执行完毕。

因为我一开始总是把多线程和异步搞混,所以我理解成多线程只是实现异步的手段,而异步是目的。

四、同异步与不同队列的组合

这里可以先打个比方,Dispatch Queue相当于是领导,而线程相当于是工人,领导可以串行发布任务,也可以并发发布任务(串行并发),工人有一人或多人(同步异步)。

1.串行队列同步执行

iOS基础:多线程-深入理解GCD_第2张图片
串行队列同步执行.png

Serial Dispatch Queue把任务一个个拿出来,线程一个个的执行拿出来的任务。

2.并行队列同步执行

Concurrent Dispatch Queue会快速的把任务都拿出来,但是线程就只有一个,还是只能一个个得执行任务,所以并没有多线程执行。

3.串行队列异步执行

Serial Dispatch Queue会把任务一个个拿出来,但拿出下一个前提是上一个任务已经被线程执行完毕了。所以这里会有多个线程来等待执行任务,但是任务却一个个出来,真是浪费了这么多线程资源啊。

4.并行队列异步执行

iOS基础:多线程-深入理解GCD_第3张图片
并行队列异步执行.png

这里任务快速拿出,线程快速执行,就实现了多线程操作,提高效率。

这样一来对于GCD的理解是否一下子清晰了~

五、GCD的小测试——线程死锁

既然对于GCD有了更加清晰的了解,那么看到有关线程死锁的问题你能否一眼看出呢。

第一种:

代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSLog(@"----------1");
    dispatch_sync(dispatch_get_main_queue(), ^{
        NSLog(@"----------2");
    });
    NSLog(@"----------3");
}

结果:

2017-04-01 15:08:49.142 MRCTest[4201:334649] ----------1

分析:
首先打印1肯定没问题,但是为什么2,3没了呢?其实是这样的:由于是主队列同步执行而且block是后下入主队列的,所以block会放到主队列的后面等待主队列执行完毕后再执行,所以2是放在3的后面的。但是主线程也在等block执行完毕,这样主线程才会继续执行。也就是说3又在等2执行完毕才会执行。所以出现了死锁。
可能有点绕,但是想通了却是很简单哟。

第二种:

代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSLog(@"----------1");
    dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
        NSLog(@"----------2");
    });
    NSLog(@"----------3");
}

结果:

2017-04-01 15:31:39.474 MRCTest[4380:348251] ----------1
2017-04-01 15:31:39.543 MRCTest[4380:348251] ----------2
2017-04-01 15:31:39.555 MRCTest[4380:348251] ----------3

分析:
本次为同步全局队列。虽然主线程队列会等待2的执行,但是2这次没有放在3的后面而是在另一个全局队列中,所以不会造成死锁。

第三种:

代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSLog(@"----------1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"----------2");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"----------3");
        });
        NSLog(@"----------4");
    });
    NSLog(@"----------5");
}

结果:

2017-04-01 15:42:41.212 MRCTest[4523:355983] ----------1
2017-04-01 15:42:41.212 MRCTest[4523:355983] ----------5
2017-04-01 15:42:41.212 MRCTest[4523:356121] ----------2
2017-04-01 15:42:41.231 MRCTest[4523:355983] ----------3
2017-04-01 15:42:41.231 MRCTest[4523:356121] ----------4

分析:
这次有点复杂了,我们慢慢来。
首先打印1,没毛病。然后将

        NSLog(@"----------2");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"----------3");
        });
        NSLog(@"----------4");

这个block放到了全局队列中,而且是异步的,说明主队列不需要等待这个block的执行,所以会打印5。
再看block中,打印2没毛病,然后又同步的把3放到了主队列后面。注意:这里看似和第一种类似好像会造成死锁,其实不是的,这次主队列其实已经运行完啦。不信你看5打印了没~

第四种:

代码:

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSLog(@"----------1");
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSLog(@"----------2");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"----------3");
        });
        NSLog(@"----------4");
    });
    NSLog(@"----------5");
    while (1) {

    }
    NSLog(@"----------6");
}

结果:

2017-04-01 15:51:47.152 MRCTest[4579:360952] ----------1
2017-04-01 15:51:47.153 MRCTest[4579:360952] ----------5
2017-04-01 15:51:47.153 MRCTest[4579:360999] ----------2

分析:
本次只是在第三种的基础上加了一个while循环。但是却只打印了152。原因其实也很简单,这次主线程没有执行完啊,6不是没打印嘛~所以3还是放在主线程队列后面,但是主线程没有执行完,不会执行3。而3是同步的,所以3不执行完4也不会打印。

小结

这种判断线程死锁的问题,最主要的还是思路需要清晰。遇到同步方法,就会阻塞后面的代码,必须先执行block中的代码。而遇到异步的方法,先不管block,反正后面的代码肯定会执行。但是执行顺序就得看加到哪个队列去了~

总结

上面的概念、代码已经解释都是我近几天的研究的成果,终于从一知半解到基本掌握了。如果你再看线程死锁那部分还是很绕的话,那就自己敲敲看,然后结合我的解释再一行行分解看,肯定可以掌握的~

更新 (17.12.04)

在看了MobileProject的代码后,发现了里面存在一些与GCD相关的代码,也把它记录下来。

一、并发队列 + 同步执行

    NSLog(@"syncConcurrent---begin");
    
    dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"syncConcurrent---end");
//    syncConcurrent---begin
//    1------{number = 1, name = main}
//    1------{number = 1, name = main}
//    2------{number = 1, name = main}
//    2------{number = 1, name = main}
//    3------{number = 1, name = main}
//    3------{number = 1, name = main}
//    syncConcurrent---end

分析:因为这里是同步执行,所以不会开启新线程,并且会阻塞当前线程。因此syncConcurrent---end会最后输出,123也会按照顺序打印,并且打印的线程都为主线程。

二、并发队列 + 异步执行

    NSLog(@"asyncConcurrent---begin");
    
    dispatch_queue_t queue= dispatch_queue_create("test.asyncqueue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncConcurrent---end");
//    asyncConcurrent---begin
//    asyncConcurrent---end
//    3------{number = 9, name = (null)}
//    3------{number = 9, name = (null)}
//    1------{number = 10, name = (null)}
//    1------{number = 10, name = (null)}
//    2------{number = 5, name = (null)}
//    2------{number = 5, name = (null)}

分析:因为这里为异步执行,不会阻塞当前线程,并具备开启新线程能力。所以asyncConcurrent---end会立马打印出来。123打印的顺序也不会固定,因为都是不同的线程同时执行打印出来的。

三、串行队列 + 同步执行

    NSLog(@"syncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("test.syncSerial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"syncSerial---end");
//    syncSerial---begin
//    1------{number = 1, name = main}
//    1------{number = 1, name = main}
//    2------{number = 1, name = main}
//    2------{number = 1, name = main}
//    3------{number = 1, name = main}
//    3------{number = 1, name = main}
//    syncSerial---end

分析:和一类似。

四、串行队列 + 异步执行

    NSLog(@"asyncSerial---begin");
    
    dispatch_queue_t queue = dispatch_queue_create("test.asyncSerial", DISPATCH_QUEUE_SERIAL);
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncSerial---end");
//    asyncSerial---begin
//    asyncSerial---end
//    1------{number = 4, name = (null)}
//    1------{number = 4, name = (null)}
//    2------{number = 4, name = (null)}
//    2------{number = 4, name = (null)}
//    3------{number = 4, name = (null)}
//    3------{number = 4, name = (null)}

分析:异步执行,因此asyncSerial---end会立马打印不需等待。因为是串行队列,123会依次放入到这个队列中,因此123顺序打印。并且个人认为,为了资源不浪费,所以只新开了一个线程。二中因为是并发队列,123一起执行,所以一个线程完成不了任务,才创建了多个线程。

五、主队列 + 同步执行

    NSLog(@"syncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_sync(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"syncMain---end");
//    syncMain---begin

分析:线程死锁,参照上面测试代码。

六、主队列 + 异步执行

    NSLog(@"asyncMain---begin");
    
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"1------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"2------%@",[NSThread currentThread]);
        }
    });
    dispatch_async(queue, ^{
        for (int i = 0; i < 2; ++i) {
            NSLog(@"3------%@",[NSThread currentThread]);
        }
    });
    
    NSLog(@"asyncMain---end");
//    asyncMain---begin
//    asyncMain---end
//    1------{number = 1, name = main}
//    1------{number = 1, name = main}
//    2------{number = 1, name = main}
//    2------{number = 1, name = main}
//    3------{number = 1, name = main}
//    3------{number = 1, name = main}

分析:和四类似。

七、全局队列+ 异步执行

    dispatch_queue_t q = dispatch_get_global_queue(0, 0);
    // 2. 异步执行
    for (int i = 0; i < 10; ++i) {
        dispatch_async(q, ^{
            NSLog(@"asyncGloba:%@ %d", [NSThread currentThread], i);
        });
    }
    NSLog(@"come here");
你猜

你可能感兴趣的:(iOS基础:多线程-深入理解GCD)