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。

Dispatch Queue1.png

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

三、同步&&异步

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

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

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

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

1.串行队列同步执行

串行队列同步执行.png

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

2.并行队列同步执行

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

3.串行队列异步执行

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

4.并行队列异步执行

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

推荐阅读更多精彩内容