GCD队列+任务(OC&Swift)

说明:本文主要用于个人能力的提高,主要参考于,Swift版本为3.0

1. 简介

什么是GCD呢?我们先看看百度百科的解释了解下概念

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

为什么要用GCD呢?

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

  • GCD可用于多核的并行运算
  • GCD会自动利用更多的CPU内核(比如双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

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

2.任务和队列

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

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

  • 同步执行(sync):只能在当前线程中执行任务,不具备开启新线程的能力
  • 异步执行(async):可以在新的线程中执行任务,具备开启新线程的能力

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

  • 并行队列(Concurrent Dispatch Queue):可以让多个任务并行(同时)执行(自动开启多个线程同时执行任务)
    • 并行功能只有在异步(dispatch_async)函数下才有效
  • 串行队列(Serial Dispatch Queue):让任务一个接着一个地执行(一个任务执行完毕后,再执行下一个任务)

3.GCD的使用步骤

GCD的使用步骤其实很简单,只有两步。

  1. 创建一个队列(串行队列或并行队列)
  2. 将任务添加到队列中,然后系统就会根据任务类型执行任务(同步执行或异步执行)

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

1. 队列的创建方法

OC版本:

  • 可以使用dispatch_queue_create来创建对象,需要传入两个参数,第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并行队列。DISPATCH_QUEUE_SERIAL表示串行队列,DISPATCH_QUEUE_CONCURRENT表示并行队列。
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT); 
  • 对于并行队列,还可以使用dispatch_get_global_queue来创建全局并行队列。GCD默认提供了全局的并行队列,需要传入两个参数。第一个参数表示队列优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT。第二个参数暂时没用,用0即可。

Swift版本:

  • 在使用Swift编码时,可以使用DispatchQueue来创建对象,第一个参数与OC相同,为标识符,其他参数均可省略。当仅有一个label参数时,默认为串行队列,当设置了attributes.concurrent时为并行队列。
// 创建串行队列
let queue = DispatchQueue(label: "test.queue")
// 创建并行队列
let queue = DispatchQueue(label: "test.queue", attributes: .concurrent)

2. 任务的创建方法

OC版本:

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

Swift版本:

// 同步执行任务创建方法
queue.sync {
    print(Thread.current)
}
// 异步执行任务创建方法
queue.async {
    print(Thread.current)
}

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

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

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

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

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

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

4. GCD的基本使用

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

1. 并行队列 + 同步执行

  • 不会开启新线程,执行完一个任务,再执行下一个任务
    OC版本:
- (void)syncConcurrentMethod{
    NSLog(@"syncConcurrentMethod---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(@"syncConcurrentMethod---end");
}

输出结果:
2017-09-19 10:57:34.223 OC-GCD[1824:54181] syncConcurrentMethod---begin
2017-09-19 10:57:34.223 OC-GCD[1824:54181] 1------{number = 1, name = main}
2017-09-19 10:57:34.223 OC-GCD[1824:54181] 1------{number = 1, name = main}
2017-09-19 10:57:34.223 OC-GCD[1824:54181] 2------{number = 1, name = main}
2017-09-19 10:57:34.223 OC-GCD[1824:54181] 2------{number = 1, name = main}
2017-09-19 10:57:34.224 OC-GCD[1824:54181] 3------{number = 1, name = main}
2017-09-19 10:57:34.224 OC-GCD[1824:54181] 3------{number = 1, name = main}
2017-09-19 10:57:34.224 OC-GCD[1824:54181] syncConcurrentMethod---end

Swift版本:

    func syncConcurrentMethod() {
        print("syncConcurrentMethod---begin")
        
        let queue = DispatchQueue(label: "test.queue", attributes: .concurrent)
        
        queue.sync {
            for _ in 0..<2 {
                print("1------",Thread.current)
            }
        }
        
        queue.sync {
            for _ in 0..<2 {
                print("2------",Thread.current)
            }
        }
        
        queue.sync {
            for _ in 0..<2 {
                print("3------",Thread.current)
            }
        }
        
        print("syncConcurrentMethod---end")
    }

输出结果:
syncConcurrentMethod---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}
syncConcurrentMethod---end

  • 并行队列 + 同步执行中可以看到,所有任务都是在主线程中执行的,由于只有一个线程,所以任务只能一个一个执行。
  • 同事我们还可以看到,所有任务都在打印的syncConcurrentMethod---beginsyncConcurrentMethod---end之间,这说明任务是添加到队列中马上执行的。

2. 并行队列 + 异步执行

  • 可同时开启多线程,任务交替执行

OC版本:

- (void)asyncConcurrentMethod{
    NSLog(@"asyncConcurrentMethod---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(@"asyncConcurrentMethod---end");
}

输出结果:
2017-09-19 11:16:35.521 OC-GCD[2301:67293] asyncConcurrentMethod---begin
2017-09-19 11:16:35.521 OC-GCD[2301:67293] asyncConcurrentMethod---end
2017-09-19 11:16:35.521 OC-GCD[2301:67348] 1------{number = 3, name = (null)}
2017-09-19 11:16:35.521 OC-GCD[2301:67719] 2------{number = 4, name = (null)}
2017-09-19 11:16:35.521 OC-GCD[2301:67720] 3------{number = 5, name = (null)}
2017-09-19 11:16:35.522 OC-GCD[2301:67348] 1------{number = 3, name = (null)}
2017-09-19 11:16:35.522 OC-GCD[2301:67719] 2------{number = 4, name = (null)}
2017-09-19 11:16:35.522 OC-GCD[2301:67720] 3------{number = 5, name = (null)}

Swift版本:

func asyncConcurrentMethod() {
        print("asyncConcurrentMethod---begin")
        
        let queue = DispatchQueue(label: "test.queue", attributes: .concurrent)
        
        queue.async {
            for _ in 0..<2 {
                print("1------",Thread.current)
            }
        }
        
        queue.async {
            for _ in 0..<2 {
                print("2------",Thread.current)
            }
        }
        
        queue.async {
            for _ in 0..<2 {
                print("3------",Thread.current)
            }
        }
        
        print("asyncConcurrentMethod---end")
    }

输出结果:
asyncConcurrentMethod---begin
asyncConcurrentMethod---end
1------ {number = 3, name = (null)}
2------ {number = 4, name = (null)}
3------ {number = 5, name = (null)}
1------ {number = 3, name = (null)}
2------ {number = 4, name = (null)}
3------ {number = 5, name = (null)}

  • 并行队列 + 异步执行中可以看出,除了主线程,又新开启了3个新线程,并且任务是交替着同时进行的。
  • 另一方面可以看出,所有任务是在打印的asyncConcurrentMethod---beginasyncConcurrentMethod---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行的。

3. 串行队列 + 同步执行

  • 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。
    OC版本:
- (void) syncSerialMethod {
    NSLog(@"syncSerialMethod---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(@"syncSerialMethod---end");
}

输出结果:
2017-09-19 11:35:00.774 OC-GCD[2699:78730] syncSerialMethod---begin
2017-09-19 11:35:00.775 OC-GCD[2699:78730] 1------{number = 1, name = main}
2017-09-19 11:35:00.775 OC-GCD[2699:78730] 1------{number = 1, name = main}
2017-09-19 11:35:00.775 OC-GCD[2699:78730] 2------{number = 1, name = main}
2017-09-19 11:35:00.775 OC-GCD[2699:78730] 2------{number = 1, name = main}
2017-09-19 11:35:00.775 OC-GCD[2699:78730] 3------{number = 1, name = main}
2017-09-19 11:35:00.776 OC-GCD[2699:78730] 3------{number = 1, name = main}
2017-09-19 11:35:00.776 OC-GCD[2699:78730] syncSerialMethod---end
Swift版本:

func syncSerialMethod() {
        
        print("syncSerialMethod---begin")
        
        let queue = DispatchQueue(label: "test.queue")
        
        queue.sync {
            for _ in 0..<2 {
                print("1------",Thread.current)
            }
        }
        
        queue.sync {
            for _ in 0..<2 {
                print("2------",Thread.current)
            }
        }
        
        queue.sync {
            for _ in 0..<2 {
                print("3------",Thread.current)
            }
        }
        
        print("syncSerialMethod---end")
        
    }

输出结果:
syncSerialMethod---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}
syncSerialMethod---end

  • 串行队列 + 同步执行可以看到,所有任务都是在主线程中执行的,并没有开启新的线程。而且由于串行队列,所以按顺序一个一个执行。
  • 同时我们还可以看到,所有任务都在打印的syncSerialMethod---beginsyncSerialMethod---end之间,说明任务是添加到队列中,并马上执行的。

4. 串行队列 + 异步执行

  • 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务
    OC版本:
- (void) asyncSerialMethod{
    NSLog(@"asyncSerialMethod---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(@"asyncSerialMethod---end");
}

输出结果:
2017-09-19 11:54:29.808 OC-GCD[3018:88451] asyncSerialMethod---begin
2017-09-19 11:54:29.808 OC-GCD[3018:88451] asyncSerialMethod---end
2017-09-19 11:54:29.808 OC-GCD[3018:88506] 1------{number = 3, name = (null)}
2017-09-19 11:54:29.809 OC-GCD[3018:88506] 1------{number = 3, name = (null)}
2017-09-19 11:54:29.809 OC-GCD[3018:88506] 2------{number = 3, name = (null)}
2017-09-19 11:54:29.809 OC-GCD[3018:88506] 2------{number = 3, name = (null)}
2017-09-19 11:54:29.809 OC-GCD[3018:88506] 3------{number = 3, name = (null)}
2017-09-19 11:54:29.809 OC-GCD[3018:88506] 3------{number = 3, name = (null)}

Swift版本:

func asyncSerialMethod() {
        
        print("asyncSerialMethod---begin")
        
        let queue = DispatchQueue(label: "test.queue")
        
        queue.async {
            for _ in 0..<2 {
                print("1------",Thread.current)
            }
        }
        
        queue.async {
            for _ in 0..<2 {
                print("2------",Thread.current)
            }
        }
        
        queue.async {
            for _ in 0..<2 {
                print("3------",Thread.current)
            }
        }
        
        print("asyncSerialMethod---end")
        
    }

输出结果:
asyncSerialMethod---begin
asyncSerialMethod---end
1------ {number = 3, name = (null)}
1------ {number = 3, name = (null)}
2------ {number = 3, name = (null)}
2------ {number = 3, name = (null)}
3------ {number = 3, name = (null)}
3------ {number = 3, name = (null)}

  • 串行队列 + 异步执行可以看到,开启了一条新线程,但是任务还是串行,所以任务是一个一个执行。
  • 另一方面可以看出,所有哦任务是在打印的asyncSerialMethod---beginasyncSerialMethod---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始异步执行的。

然后讲讲刚才提到过的特殊队列——主队列

  • 主队列:GCD自带的一种特殊的串行队列
    • 所有放在主队列中的任务,都会放到主线程中执行
    • OC可以使用dispatch_get_main_queue()获得主队列,Swift可以使用DispatchQueue.main获得

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

5. 主队列 + 同步执行

  • 相互等待,造成死锁(在主线程中调用时)

OC版本:

- (void)syncMainMethod{
    NSLog(@"syncMainMethod---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(@"syncMainMethod---end");
}

输出结果:
2017-09-19 14:20:14.826 OC-GCD[4057:146416] syncMainMethod---begin
(lldb)

Swift版本:

func syncMainMethod() {
        
        print("syncMainMethod---begin")
        
        let queue = DispatchQueue.main
        
        queue.sync {
            for _ in 0..<2 {
                print("1------",Thread.current)
            }
        }
        
        queue.sync {
            for _ in 0..<2 {
                print("2------",Thread.current)
            }
        }
        
        queue.sync {
            for _ in 0..<2 {
                print("3------",Thread.current)
            }
        }
        
        print("syncMainMethod---end")
        
    }

输出结果:
syncMainMethod---begin
(lldb)

这时候,我们可以发现,在主线程中使用主队列 + 同步执行,任务不再执行了,而syncMainMethod---end也没有打印了,这是什么原因导致的呢?

这是因为我们在主线中执行这段代码。我们把任务放到了主队列中,也就是放到了主线程的队列中。而同步执行有个特点,就是对于任务是立马执行的。那么当我们把第一个任务放进主队列中,它会立即执行。但是对于主线程现在正在处理syncMainMethod方法,而任务又需要等syncMainMethod方法执行结束才能执行。而syncMainMethod执行到第一个任务的时候,又要等第一个任务执行完才能玩下执行第二和第三个任务。简单来说,就是syncMainMethod方法结束后才能执行第一个同步任务,而第一个同步任务又在syncMainMethod方法中,需要执行了本任务之后,才能继续执行其他任务,直到syncMainMethod方法完全结束,相互等待,造成了死锁

那么,现在的情况就是第一个任务都在等对方执行完毕,造成了死锁,所以我们的任务就执行不了,而且syncMainMethod---end也没打印

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

  • 不会开启新线程,执行完一个任务,再执行下一个任务(在其他线程中调用)

OC版本:

    dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
    
    dispatch_async(queue, ^{
        [self syncMainMethod];
    });

输出结果:
2017-09-19 14:51:03.723 OC-GCD[4483:161903] syncMainMethod---begin
2017-09-19 14:51:03.723 OC-GCD[4483:161788] 1------{number = 1, name = main}
2017-09-19 14:51:03.724 OC-GCD[4483:161788] 1------{number = 1, name = main}
2017-09-19 14:51:03.724 OC-GCD[4483:161788] 2------{number = 1, name = main}
2017-09-19 14:51:03.724 OC-GCD[4483:161788] 2------{number = 1, name = main}
2017-09-19 14:51:03.724 OC-GCD[4483:161788] 3------{number = 1, name = main}
2017-09-19 14:51:03.724 OC-GCD[4483:161788] 3------{number = 1, name = main}
2017-09-19 14:51:03.724 OC-GCD[4483:161903] syncMainMethod---end

Swift版本:

        let queue = DispatchQueue(label: "test.queue", attributes: .concurrent)
        queue.async {
            self.syncMainMethod()
        }

输出结果:
syncMainMethod---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}
syncMainMethod---end

  • 在其他线程中使用主队列 + 同步执行可以看到:所有任务都是在主线程中执行的,并没有开启新线程。而且由于主队列是串行队列,可以按照顺序一个一个执行。
  • 同时我们还可以看到,所有任务都在打印的syncConcurrent---beginsyncConcurrent---end之间,说明任务是添加到队列中马上执行的。

6. 主队列 + 异步执行

  • 只有在主线程中执行任务,执行完一个任务,再执行下一个任务

OC版本:

- (void)asyncMainMethod{
    NSLog(@"asyncMainMethod---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(@"asyncMainMethod---end");
}

输出结果:
2017-09-19 22:30:10.807 OC-GCD[1151:23348] asyncMainMethod---begin
2017-09-19 22:30:10.808 OC-GCD[1151:23348] asyncMainMethod---end
2017-09-19 22:30:10.809 OC-GCD[1151:23348] 1------{number = 1, name = main}
2017-09-19 22:30:10.810 OC-GCD[1151:23348] 1------{number = 1, name = main}
2017-09-19 22:30:10.810 OC-GCD[1151:23348] 2------{number = 1, name = main}
2017-09-19 22:30:10.810 OC-GCD[1151:23348] 2------{number = 1, name = main}
2017-09-19 22:30:10.810 OC-GCD[1151:23348] 3------{number = 1, name = main}
2017-09-19 22:30:10.810 OC-GCD[1151:23348] 3------{number = 1, name = main}

Swift版本:

    func asyncMainMethod() {
        
        print("asyncMainMethod---begin")
        
        let queue = DispatchQueue.main
        
        queue.async {
            for _ in 0..<2 {
                print("1------",Thread.current)
            }
        }
        
        queue.async {
            for _ in 0..<2 {
                print("2------",Thread.current)
            }
        }
        
        queue.async {
            for _ in 0..<2 {
                print("3------",Thread.current)
            }
        }
        
        print("asyncMainMethod---end")
        
    }

输出结果:
asyncMainMethod---begin
asyncMainMethod---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}

  • 我们可以发现所有任务都在主线程钟,虽然是异步执行,具备开启线程的能力,但是因为是主队列,所以所有任务都在主线程中,并且一个接一个执行。
  • 另一方面可以看出,所有任务是在打印的asyncMainMethod---beginasyncMainMethod---end之后才开始执行的。说明任务不是马上执行,而是将所有任务添加到队列之后才开始同步执行。

你可能感兴趣的:(GCD队列+任务(OC&Swift))