iOS多线程详解(三)--- GCD

2017-11-17  本文已影响114人  WQ_UESTC

1、简介

什么是GCD呢?先来看看百度百科的解释

引自百度百科
Grand Central Dispatch (GCD) 是Apple开发的一个多核编程的较新的解决方法。它主要用于优化应用程序以支持多核处理器以及其他对称多处理系统。它是一个在线程池模式的基础上执行的并行任务。在Mac OS X 10.6雪豹中首次推出,也可在IOS 4及以上版本使用。

为什么要用GCD呢?

因为GCD有很多好处啊,具体如下:

既然GCD有这么多的好处,那么下面我们就来系统的学习一下GCD的使用方法。

2、任务和队列

学习GCD之前,先来了解GCD中两个核心概念:任务和队列。

任务:就是执行操作的意思,换句话说就是你在线程中执行的那段代码。在GCD中是放在block中的。执行任务有两种方式:同步执行和异步执行。两者的主要区别是:是否具备开启新线程的能力。

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

3、GCD的使用步骤

GCD的使用步骤其实很简单,只有两步。
1、创建一个队列(串行队列或并行队列)
2、将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

下边来看看队列的创建方法和任务的创建方法。

3-1 队列的创建方法

// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

3-2 任务的创建方法

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});

虽然使用GCD只需两步,但是既然我们有两种队列,两种任务执行方式,那么我们就有了四种不同的组合方式。这四种不同的组合方式是

1、并行队列 + 同步执行
2、并行队列 + 异步执行
3、串行队列 + 同步执行
4、串行队列 + 异步执行

实际上,我们还有一种特殊队列是主队列,那样就有六种不同的组合方式了。

1、主队列 + 同步执行
2、主队列 + 异步执行

那么这几种不同组合方式各有什么区别呢,这里为了方便,先上结果,再来讲解。为图省事,直接查看表格结果,然后可以跳过4. GCD的基本使用了。

列表 并行队列 串行队列 主队列
同步(sync) 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务 没有开启新线程,串行执行任务
异步(async) 有开启新线程,并行执行任务 有开启新线程(1条),串行执行任务 没有开启新线程,串行执行任务

下边我们来分别讲讲这几种不同的组合方式的使用方法。

4、GCD的基本使用

先来讲讲并行队列的两种使用方法。

4-1 并行队列 + 同步执行

- (void) syncConcurrent
{
    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");
}

输出结果
2017-11-17 16:52:47.271328+0800 test[20243:1406482] syncConcurrent---begin
2017-11-17 16:52:47.271538+0800 test[20243:1406482] 1------<NSThread: 0x60000007dc00>{number = 1, name = main}
2017-11-17 16:52:47.271712+0800 test[20243:1406482] 1------<NSThread: 0x60000007dc00>{number = 1, name = main}
2017-11-17 16:52:47.271836+0800 test[20243:1406482] 2------<NSThread: 0x60000007dc00>{number = 1, name = main}
2017-11-17 16:52:47.271964+0800 test[20243:1406482] 2------<NSThread: 0x60000007dc00>{number = 1, name = main}
2017-11-17 16:52:47.272190+0800 test[20243:1406482] 3------<NSThread: 0x60000007dc00>{number = 1, name = main}
2017-11-17 16:52:47.272308+0800 test[20243:1406482] 3------<NSThread: 0x60000007dc00>{number = 1, name = main}
2017-11-17 16:52:47.272410+0800 test[20243:1406482] syncConcurrent---end

4-2 并行队列+异步执行

- (void) asyncConcurrent
{
    NSLog(@"asyncConcurrent---begin");

    dispatch_queue_t queue= dispatch_queue_create("test.queue", 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");
}

输出结果:
2017-11-17 17:00:31.952632+0800 test[20661:1444534] asynConcurrent--begin
2017-11-17 17:00:31.952869+0800 test[20661:1444534] asyncConcurrent---end
2017-11-17 17:00:31.953029+0800 test[20661:1444879] 2------<NSThread: 0x600000276280>{number = 4, name = (null)}
2017-11-17 17:00:31.953034+0800 test[20661:1444877] 3------<NSThread: 0x60400027a7c0>{number = 5, name = (null)}
2017-11-17 17:00:31.953059+0800 test[20661:1444876] 1------<NSThread: 0x60400027a880>{number = 3, name = (null)}
2017-11-17 17:00:31.953207+0800 test[20661:1444879] 2------<NSThread: 0x600000276280>{number = 4, name = (null)}
2017-11-17 17:00:31.953558+0800 test[20661:1444877] 3------<NSThread: 0x60400027a7c0>{number = 5, name = (null)}
2017-11-17 17:00:31.953588+0800 test[20661:1444876] 1------<NSThread: 0x60400027a880>{number = 3, name = (null)}

接下来再来讲讲串行队列的执行方法。

4-3 串行队列+同步执行

- (void) syncSerial
{
    NSLog(@"syncSerial---begin");

    dispatch_queue_t queue = dispatch_queue_create("test.queue", 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");
}

输出结果为:
2017-11-17 17:04:43.580152+0800 test[20891:1466709] syncSerial---begin
2017-11-17 17:04:43.580362+0800 test[20891:1466709] 1------<NSThread: 0x60400007b8c0>{number = 1, name = main}
2017-11-17 17:04:43.580540+0800 test[20891:1466709] 1------<NSThread: 0x60400007b8c0>{number = 1, name = main}
2017-11-17 17:04:43.580677+0800 test[20891:1466709] 2------<NSThread: 0x60400007b8c0>{number = 1, name = main}
2017-11-17 17:04:43.580891+0800 test[20891:1466709] 2------<NSThread: 0x60400007b8c0>{number = 1, name = main}
2017-11-17 17:04:43.581039+0800 test[20891:1466709] 3------<NSThread: 0x60400007b8c0>{number = 1, name = main}
2017-11-17 17:04:43.581216+0800 test[20891:1466709] 3------<NSThread: 0x60400007b8c0>{number = 1, name = main}
2017-11-17 17:04:43.581351+0800 test[20891:1466709] syncSerial---end

4-4 串行队列+异步执行

- (void) asyncSerial
{
    NSLog(@"asyncSerial---begin");

    dispatch_queue_t queue = dispatch_queue_create("test.queue", 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");
}

输出结果:
2017-11-17 17:10:27.247671+0800 test[21189:1494834] asyncSerial---begin
2017-11-17 17:10:27.247891+0800 test[21189:1494834] asyncSerial---end
2017-11-17 17:10:27.247999+0800 test[21189:1495185] 1------<NSThread: 0x600000264a40>{number = 3, name = (null)}
2017-11-17 17:10:27.248152+0800 test[21189:1495185] 1------<NSThread: 0x600000264a40>{number = 3, name = (null)}
2017-11-17 17:10:27.248490+0800 test[21189:1495185] 2------<NSThread: 0x600000264a40>{number = 3, name = (null)}
2017-11-17 17:10:27.248849+0800 test[21189:1495185] 2------<NSThread: 0x600000264a40>{number = 3, name = (null)}
2017-11-17 17:10:27.250074+0800 test[21189:1495185] 3------<NSThread: 0x600000264a40>{number = 3, name = (null)}
2017-11-17 17:10:27.250364+0800 test[21189:1495185] 3------<NSThread: 0x600000264a40>{number = 3, name = (null)}

下边讲讲刚才我们提到过的特殊队列——主队列。

我们再来看看主队列的两种组合方式。

4-5 主队列+同步执行

- (void)syncMain
{
    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");
}

输出结果:
2017-11-17 17:24:52.448959+0800 test[21933:1559721] syncMain---begin

这时候,我们惊奇的发现,在主线程中使用主队列 + 同步执行,任务不再执行了,而且syncMain---end也没有打印。这是为什么呢?

这是因为我们在主线程中执行这段代码。我们把任务放到了主队列中,也就是放到了主线程的队列中。而同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它就会立马执行。但是主线程现在正在处理syncMain方法,所以任务需要等syncMain执行完才能执行。而syncMain执行到第一个任务的时候,又要等第一个任务执行完才能往下执行第二个和第三个任务。

那么,现在的情况就是syncMain方法和第一个任务都在等对方执行完毕。这样大家互相等待,所以就卡住了,所以我们的任务执行不了,而且syncMain---end也没有打印。

要是如果不再主线程中调用,而在其他线程中调用会如何呢?

dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{
    [self syncMain];
});

输出结果:
2017-11-17 17:32:16.099866+0800 test[22323:1595837] syncMain---begin
2017-11-17 17:32:16.103426+0800 test[22323:1595605] 1------<NSThread: 0x600000260440>{number = 1, name = main}
2017-11-17 17:32:16.103608+0800 test[22323:1595605] 1------<NSThread: 0x600000260440>{number = 1, name = main}
2017-11-17 17:32:16.105070+0800 test[22323:1595605] 2------<NSThread: 0x600000260440>{number = 1, name = main}
2017-11-17 17:32:16.105447+0800 test[22323:1595605] 2------<NSThread: 0x600000260440>{number = 1, name = main}
2017-11-17 17:32:16.106557+0800 test[22323:1595605] 3------<NSThread: 0x600000260440>{number = 1, name = main}
2017-11-17 17:32:16.106743+0800 test[22323:1595605] 3------<NSThread: 0x600000260440>{number = 1, name = main}
2017-11-17 17:32:16.107063+0800 test[22323:1595837] syncMain---end

4-6 主队列+异步执行

- (void)asyncMain
{
    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");
}

输出结果:
2017-11-17 17:42:26.447518+0800 test[22856:1646943] asyncMain---begin
2017-11-17 17:42:26.447710+0800 test[22856:1646943] asyncMain---end
2017-11-17 17:42:26.451464+0800 test[22856:1646843] 1------<NSThread: 0x604000068cc0>{number = 1, name = main}
2017-11-17 17:42:26.451969+0800 test[22856:1646843] 1------<NSThread: 0x604000068cc0>{number = 1, name = main}
2017-11-17 17:42:26.453116+0800 test[22856:1646843] 2------<NSThread: 0x604000068cc0>{number = 1, name = main}
2017-11-17 17:42:26.453265+0800 test[22856:1646843] 2------<NSThread: 0x604000068cc0>{number = 1, name = main}
2017-11-17 17:42:26.453396+0800 test[22856:1646843] 3------<NSThread: 0x604000068cc0>{number = 1, name = main}
2017-11-17 17:42:26.453506+0800 test[22856:1646843] 3------<NSThread: 0x604000068cc0>{number = 1, name = main}

4-7 总结

同步和异步,串行和并行的区别

  1. 简单来说,同步和异步是针对线程来说的。前面提到过,同步不具备开启新线程的能力,而异步具备开启新线程的能力。这里要注意的是,异步具备开启新线程的能力,但是不一定就会开启新线程。例如之前讲到的主队列+异步执行的情况,就没有开启新线程。另外,同步情况下任务添加到队列中会马上执行,而异步情况下任务添加到队列中不会马上执行。

  2. 串行和并行是相对于队列,或者说任务来说的,是任务的执行先后顺序,不关系到线程。并行队列可以让多个任务并行(同时)执行。这里要注意的是,是可以让多个任务并行执行,而不是一定会并行执行。并行功能只有在异步情况下才有效。串行是让任务一个接着一个地执行。

弄懂了难理解、绕来绕去的队列+任务之后,我们来学习一个简单的东西——GCD线程之间的通讯。

5、GCD线程之间的通讯

在iOS开发过程中,我们一般在主线程里边进行UI刷新,例如:点击、滚动、拖拽等事件。我们通常把一些耗时的操作放在其他线程,比如说图片下载、文件上传等耗时操作。而当我们有时候在其他线程完成了耗时操作时,需要回到主线程,那么就用到了线程之间的通讯。

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    for (int i = 0; i < 2; ++i) {
        NSLog(@"1------%@",[NSThread currentThread]);
    }

    // 回到主线程
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"2-------%@",[NSThread currentThread]);
    });
});

输出结果:
2017-11-17 17:49:36.996067+0800 test[23236:1682181] 1------<NSThread: 0x60400026e780>{number = 3, name = (null)}
2017-11-17 17:49:36.996359+0800 test[23236:1682181] 1------<NSThread: 0x60400026e780>{number = 3, name = (null)}
2017-11-17 17:49:37.004020+0800 test[23236:1681837] 2-------<NSThread: 0x600000072580>{number = 1, name = main}

6、GCD的其他方法

6-1 GCD的栅栏方法dispatch_barrier_async

- (void)barrier
{
    dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);

    dispatch_async(queue, ^{
        NSLog(@"----1-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----2-----%@", [NSThread currentThread]);
    });

    dispatch_barrier_async(queue, ^{
        NSLog(@"----barrier-----%@", [NSThread currentThread]);
    });

    dispatch_async(queue, ^{
        NSLog(@"----3-----%@", [NSThread currentThread]);
    });
    dispatch_async(queue, ^{
        NSLog(@"----4-----%@", [NSThread currentThread]);
    });
}

输出结果:
2017-11-17 17:58:04.391001+0800 test[23685:1722668] ----1-----<NSThread: 0x600000279240>{number = 3, name = (null)}
2017-11-17 17:58:04.390980+0800 test[23685:1722665] ----2-----<NSThread: 0x6000002792c0>{number = 4, name = (null)}
2017-11-17 17:58:04.392417+0800 test[23685:1722665] ----barrier-----<NSThread: 0x6000002792c0>{number = 4, name = (null)}
2017-11-17 17:58:04.395156+0800 test[23685:1722665] ----3-----<NSThread: 0x6000002792c0>{number = 4, name = (null)}
2017-11-17 17:58:04.395196+0800 test[23685:1722668] ----4-----<NSThread: 0x600000279240>{number = 3, name = (null)}

6-2 GCD的延迟执行方法dispatch_after

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

6-3 GCD的一次性代码(只执行一次)dispatch_once

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
});

6-4 GCD的快速迭代方法dispatch_apply

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_apply(6, queue, ^(size_t index) {
    NSLog(@"%zd------%@",index, [NSThread currentThread]);
});

输出结果:
2017-11-17 18:11:18.379055+0800 test[24368:1786316] 0------<NSThread: 0x60000007d940>{number = 1, name = main}
2017-11-17 18:11:18.379068+0800 test[24368:1786663] 1------<NSThread: 0x600000471b40>{number = 3, name = (null)}
2017-11-17 18:11:18.379069+0800 test[24368:1786660] 3------<NSThread: 0x6040002758c0>{number = 4, name = (null)}
2017-11-17 18:11:18.379072+0800 test[24368:1786662] 2------<NSThread: 0x604000275080>{number = 5, name = (null)}
2017-11-17 18:11:18.379279+0800 test[24368:1786316] 4------<NSThread: 0x60000007d940>{number = 1, name = main}
2017-11-17 18:11:18.379285+0800 test[24368:1786663] 5------<NSThread: 0x600000471b40>{number = 3, name = (null)}

从输出结果中前边的时间中可以看出,几乎是同时遍历的。

6-5 GCD的队列组dispatch_group

dispatch_group_t group =  dispatch_group_create();

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});

dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 执行1个耗时的异步操作
});

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    // 等前面的异步操作都执行完毕后,回到主线程...
});

7、小结

本节介绍了iOS开发中较为常用的GCD。其中,不止是多线程相关的知识,其他的GCD知识也都要掌握。在实际的开发过程中,GCD会经常遇到。因此,这部分一定要熟练掌握。下节介绍iOS多线程中另外一个常用的方式NSOperation。

相关系列文章

iOS多线程详解(一)--- 多线程基础
iOS多线程详解(二)--- pthread&NSThread
iOS多线程详解(三)--- GCD
iOS多线程详解(四)--- NSOperation
iOS多线程详解(五)--- 线程安全(锁的创建)
iOS多线程详解(六)--- 线程安全(Property)

参考文献

本文大部分来自下面的链接,特向作者表示感谢
http://www.jianshu.com/p/2d57c72016c6

上一篇下一篇

猜你喜欢

热点阅读