GCD相关学习整理

GCD多线程整理学习

GCD简介

iOS中多核编程的解决方法。

主要用于优化应用程序以及支持多核处理器以及其他对称多处理系统。iOS4推出

优点

<pre>

1.可用于多核并行运算

2.自动利用更多CPU内核(双核,四核等)

3.GCD会自动管理线程生命周期(线程的创建和销毁、调度任务)

4.不需要编写线程管理代码

</pre>

GCD 任务和队列

任务

就是执行操作的意思,也就是线程中执行的代码

<p>

执行代码又分:同步执行、异步执行

</p>

  • 同步执行

    • 同步添加任务到指定队列中,在添加的任务执行结束之前,会一直等待,直到队列里面的任务完成之后再继续执行

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

  • 异步执行

    • 异步添加任务到指定队列中,它不会做任何等待,可以继续执行任务

    • 可以在新的线程中执行任务,具备开启新线程的能力

注意:异步执行(asyn)虽然具有开启线程的能力,但是并不一定开启新线程。这跟任务所指定的队列类型有关。

队列(Dispatch Queue)

执行任务的等待队列,属于特殊的线性列,FIFO原则如下图

队列图

队列又分串行队列并发队列,两者符合FIFO,区别在于:执行顺序不同,开启线程数不同

  • 串行队列(Serial Dispatch Queue)

    • 任务逐一执行,一个完成才到下一个

    • 只开启一个新线程

  • 并发队列(Concurrent Dispatch Queue)

    • 可以让多任务并发(同时)执行

    • 可以开启多个线程,并同时执行任务

注意:并发队列的并发功能只有异步(dispatch_asyn)函数下有效

区别如图

串行队列
并发队列

GCD使用

使用步骤:
  1. 创建队列(串行、并发)

  2. 将任务追加到队列中,系统会根据任务类型执行任务(同步执行或异步执行)

队列创建/获取

  • 使用 dispatch_queue_create 创建

//串行队列的创建方法

  dispatch_queue_t queue = dispatch_queue_create("key", DISPATCH_QUEUE_SERIAL);

// 并发队列的创建方法

dispatch_queue_t queue = dispatch_queue_create("key", DISPATCH_QUEUE_CONCURRENT);

  • 对于串行队列,特殊的串行队列,主队列 Main Dispatch Queue

    • 所有主队列任务都会在主线程执行

    • 使用 dispatch_get_main_queue()获取主队列

  • 对于并发队列,GCD的默认全局并发队列 Global Dispatch Queue

    • 使用dispatch_get_global_queue获取,第一个参数,优先级
    
    

// 全局并发队列的获取方法

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);




####任务的创建方法

1. 同步执行任务:`dispatch_sync`

2. 异步执行任务:`dispatch_async`

// 同步执行任务创建方法

dispatch_sync(queue, ^{

// 这里放同步执行任务代码

});

// 异步执行任务创建方法

dispatch_async(queue, ^{

// 这里放异步执行任务代码

});


根据队列可分为四种情况

> 1. 同步执行+并发队列

> 2. 异步执行+并发队列

> 3. 同步执行+串行队列

> 4. 异步执行+串行队列

对于之前的两个特殊队列,全局并发队列、主队列。全局并发队列可以作为普通并发队列使用,但是主队列有点特殊,故又多了两种组合

> 5. 同步执行+主队列

> 6. 异步执行+主队列

总结:

区别 | 并发队列 | 串行队列 | 主队列

:--| :-- | :-- | :--

同步(sync)|不开新线程,串行|不开新线程,串行|主线程调用:死锁卡死;<p>其他线程:不开新线程,串行

异步(async)|有开新线程,并发|有开新线程(1条),串行|不开新线程,串行

###GCD的基本使用

####1.同步执行 + 并发队列

* 在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务,FIFO原则

/**

  • 同步执行 + 并发队列

  • 特点:在当前线程中执行任务,不会开启新线程,执行完一个任务,再执行下一个任务。

*/

  • (void)syncConcurrent {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

    NSLog(@"syncConcurrent---begin");

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);



dispatch_sync(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_sync(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_sync(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



NSLog(@"syncConcurrent---end");

}


输出结果:

> <pre>

currentThread—-{number = 1, name = main}

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

</pre>

结论:

1. 所有任务都在当前线程执行,没有开启新线程(`同步执行`不具备开启新线程的能力)

2. 所有任务都在`syncConcurrent—-begin`和`syncConcurrent—-end`之间执行(`同步任务`需要等待队列的任务执行结束)

3. 任务按顺序执行的。原因:虽然`并发队列`可以开启多个线程,并且同时执行多个任务,但是因为本身不能创建新线程,只有当前线程这一个线程(`同步任务`不具备开启新线程的能力),所以也就不存在并发。而且当前线程只有等待当前队列中正在执行的任务执行完毕之后才能继续接着执行下面的操作(`同步任务`需要等待队列的任务执行结束)。所以任务只能一个接着一个按顺序执行,不能同时被执行

>综上所述,`同步执行`无开新线程能力,导致就算是并发队列也不能只能在当前线程执行,从而导致只能串行执行任务,不具备并发能力

####2.异步执行 + 并发队列

* 可以开启多个线程,任务交替(同时执行)

/**

  • 异步执行 + 并发队列

  • 特点:可以开启多个线程,任务交替(同时)执行。

*/

  • (void)asyncConcurrent {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

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

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);



dispatch_async(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_async(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_async(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



NSLog(@"asyncConcurrent---end");

}


输出结果

><pre>

currentThread—-{number = 1, name = main}

asyncConcurrent—-begin

asyncConcurrent—-end

2—-{number = 5, name = (null)}

3—-{number = 4, name = (null)}

1—-{number = 3, name = (null)}

3—-{number = 4, name = (null)}

1—-{number = 3, name = (null)}

2—-{number = 5, name = (null)}

</pre>

结论:

1. 除了当前线程,系统又开了3个线程,并且任务交替/同时执行。(`异步执行`具备开启新线程的能力,且`并发队列`可开启多个线程,同时执行多个任务)

2. 所有任务都在`syncConcurrent—-begin`和`syncConcurrent—-end`之后才执行的。说明当前线程没有等待,而是直接开启了新线程,在新线程中执行任务(`异步执行`不做等待,可以继续执行任务)

>综上所述,`异步执行`有开启新线程能力,所以并发队列才能开启多个线程,`异步执行`并不会等待,而是继续执行当前任务

####3.同步执行 + 串行队列

* 不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

/**

  • 同步执行 + 串行队列

  • 特点:不会开启新线程,在当前线程执行任务。任务是串行的,执行完一个任务,再执行下一个任务。

*/

  • (void)syncSerial {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

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

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);



dispatch_sync(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

dispatch_sync(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

dispatch_sync(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



NSLog(@"syncSerial---end");

}


输出结果

<pre>

currentThread—-{number = 1, name = main}

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

</pre>

结论:

1. 所有任务都是在当前线程执行,并没有开启新的线程(`同步执行`不具备开启新线程的能力)

2. 所有任务都在`syncConcurrent—-begin`和`syncConcurrent—-end`之间执行的(`同步线程`需要等待队列的任务执行结束)。

3. 任务是按顺序执行的(`串行队列`每次只有一个任务被执行,任务一个接一个按顺序执行)

>综上所述,`同步执行`没有开启新线程能力,`同步执行`需要等待队列任务执行完才能继续执行

####4.异步执行 + 串行队列

* 会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务

/**

  • 异步执行 + 串行队列

  • 特点:会开启新线程,但是因为任务是串行的,执行完一个任务,再执行下一个任务。

*/

  • (void)asyncSerial {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

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

dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_SERIAL);



dispatch_async(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

dispatch_async(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

dispatch_async(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



NSLog(@"asyncSerial---end");

}


输出结果

<pre>

currentThread—-{number = 1, name = main}

asyncSerial—-begin

asyncSerial—-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)}

</pre>

结论:

1. 开启了一条新线程(`异步线程`具备开启新线程的能力,`串行队列`只开启一个线程)

2. 所有任务都在`syncConcurrent—-begin`和`syncConcurrent—-end`之后才执行的(`异步执行`不会做任何等待,可以继续执行任务)

3. 任务是按顺序执行的(`串行队列`每次只有一个任务被执行,任务一个接一个按顺序执行)

>综上所述,`异步执行`具备开启新线程能力,但不会等待,`串行队列`只开启一条新线程执行任务

####5.同步执行 + 主队列

**主队列:**

* GCD自带的一种特殊的串行队列

* 所有放在主队列中的任务,都会放在主线程中执行

* 使用 `dispatch_get_main_queue()`获得主队列

####5.1在主线程中调用 <font size = 3 /font>`同步执行 + 主队列`</font>

* 互相等待卡死

/**

  • 同步执行 + 主队列

  • 特点(主线程调用):互等卡主不执行。

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

*/

  • (void)syncMain {
NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程

NSLog(@"syncMain---begin");



dispatch_queue_t queue = dispatch_get_main_queue();



dispatch_sync(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_sync(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_sync(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



NSLog(@"syncMain---end");

}


输出结果

<pre>

currentThread—-{number = 1, name = main}

syncMain—-begin

(lldb)

</pre>

结论:

1. 在主线程中使用`同步执行 + 主队列`,追加到主线程的任务1,2,3都不再执行了,而且`syncMain---end`也没有打印,还报崩溃

**原因:**这是因为我们在主线程中执行`syncMain`方法,相当于把`syncMain`任务放到了主线程的队列中。而`同步线程`会等待当前队列中的任务执行完毕,才会接着执行。那么当我们把`任务1`追加到主队列中,`任务1`就在等待主线程处理完`sysncMain`任务,而`syncMain`任务需要等待`任务1`执行完毕才能接着执行。那么,现在的情况就是`syncMain`任务和`任务1`都在等对方执行完毕,相互等待,所以就卡死了

####5.2 在其他线程中调用 <font size = 3 /font>`同步执行 + 主队列`<\font>

* 不会开启新线程,执行完一个任务,再执行下一个任务

// 使用 NSThread 的 detachNewThreadSelector 方法会创建线程,并自动启动线程执行

selector 任务

[NSThread detachNewThreadSelector:@selector(syncMain) toTarget:self withObject:nil];


输出结果

<pre>

currentThread—-{number = 3, name = (null)}

syncMain—-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}

syncMain—-end

</pre>

结论:

1. 所有任务都是在主线程(非当前线程)中执行,没有开启新线程(所有在`主队列`的任务都会在`主线程`执行)

2. 所有任务都在`syncConcurrent—-begin`和`syncConcurrent—-end`之间执行(`同步任务`需要等待队列的任务执行结束)

3. 任务是按顺序执行的(`主队列`是`串行队列`,每次只有一个任务执行,任务一个接一个执行)

为何不会卡住?

因为`sysncMain`任务放在其他线程里,而且任务1,2,3都在追加到主队列中,这三个任务会在主线程中执行。`syncMain`任务在其他线程中执行到追加 任务1 到主队列中,因为主队列现在没有正在执行的任务,所以会直接执行主队列的 任务1,之后 2,3 不会卡死

###6 异步执行 + 主队列

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

/**

  • 异步执行 + 主队列

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

*/

  • (void)asyncMain {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

    NSLog(@"asyncMain---begin");

dispatch_queue_t queue = dispatch_get_main_queue();



dispatch_async(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_async(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_async(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



NSLog(@"asyncMain---end");

}


输出结果

<pre>

currentThread—-{number = 1, name = main}

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}

</pre>

结论

1. 所有任务都在当前线程(主线程)执行,并没有开启新的线程(虽然`异步执行`具备开启线程的能力,但因为是主队列,所以所有任务都是在主线程中)

2. 所有任务都在`syncConcurrent—-begin`和`syncConcurrent—-end`之后执行(异步执行不会做任何等待,可以继续执行任务)

3. 任务是按顺序执行的(因为主队列是`串行队列`,每次只能执行一个任务)

###线程通讯

通过 `dispatch_async`、`dispatch_sync`进行通讯

###GCD的其他方法

####1.GCD 栅栏方法:dispatch_barrier_async

* 有时我们需要异步执行两组操作,而且第一组操作执行完之后,才能开始执行第二组操作。这样我们就需要一个相当于 `栅栏` 一样的一个方法将两组异步执行的操作组给分割起来,当然的操作组里面可以包含一个或多个任务,这就需要用到`dispatch_barrier_async`方法在两个操作组间形成栅栏。

<p>`dispatch_barrier_async`函数会等待前边追加到并发队列中的任务全部执行完毕之后,再将指定的任务追加到该异步队列中。然后在`dispatch_barrier_async`函数追加的任务执行完毕之后,异步队列才恢复为一般动作,接着追加任务到该异步队列并开始执行。如下图![图](http://upload-images.jianshu.io/upload_images/10443211-27884f07b52dfa89.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

代码如下

/**

  • 栅栏方法 dispatch_barrier_async

*/

  • (void)barrier {

    dispatch_queue_t queue = dispatch_queue_create("net.bujige.testQueue", DISPATCH_QUEUE_CONCURRENT);

dispatch_async(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

dispatch_async(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_barrier_async(queue, ^{

    // 追加任务 barrier

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"barrier---%@",[NSThread currentThread]);// 打印当前线程

    }

});



dispatch_async(queue, ^{

    // 追加任务3

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

dispatch_async(queue, ^{

    // 追加任务4

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"4---%@",[NSThread currentThread]);      // 打印当前线程

    }

});

}


输出结果:

<pre>

1—-{number = 4, name = (null)}

2—-{number = 3, name = (null)}

1—-{number = 4, name = (null)}

2—-{number = 3, name = (null)}

barrier—-{number = 4, name = (null)}

barrier—-{number = 4, name = (null)}

4—-{number = 3, name = (null)}

3—-{number = 4, name = (null)}

4—-{number = 3, name = (null)}

3—-{number = 4, name = (null)}

</pre>

结论:

1. 在执行完栅栏前面的操作之后,才执行栅栏操作,最后再执行后边的操作

####2.GCD 延时方法:dispatch_after

`dispatch_after`:在指定时间之后执行某个任务。

需要注意的是:`dispatch_after`函数并不是在指定时间之后才开始执行处理,而是在指定时间之后**<a>将任务追加到主队列</a>**中。严格来说,这个时间并不绝对准确。

####3.GCD 一次性代码(只执行一次):dispatch_once

* 我们在创建单例、或者有整个程度运行过程中只执行一次的代码时使用

* `dispatch_once`函数能保证代码只执行一次,并且即使在多线程的环境下也能保证线程安全

/**

  • 一次性代码(只执行一次)dispatch_once

*/

  • (void)once {

    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{

      // 只执行1次的代码(这里面默认是线程安全的)
    

    });

}


####4.GCD 快速迭代方法:dispatch_apply

* 通常我们会用 for循环 遍历,但是GCD给我们提供了快速迭代的函数`dispatch_apply`。`dipatch_apply`按照指定的次数将指定的任务追加到指定的队列中,并等待全部队列执行结束

如果是在串行队列中使用`dipatch_apply`,那么就跟 for 循环一样,按顺序同步执行。可这样就体现不出快速迭代的意义了。

<p>我们可以利用并发队列进行异步执行。比如说遍历0-5这6个数字,for循环的做法是每次取出一个元素,逐个遍历。`dispatch_apply`可以在多个线程中同时(异步)遍历多个数字。

<p>还有一点,无论是在串行队列,还是异步队列中,dipatch_apply 都会等待全部任务执行完毕,这点就像是同步操作,也像是队列组中的`dispatch_group_wait`方法

/**

  • 快速迭代方法 dispatch_apply

*/

  • (void)apply {

    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

NSLog(@"apply---begin");

dispatch_apply(6, queue, ^(size_t index) {

    NSLog(@"%zd---%@",index, [NSThread currentThread]);

});

NSLog(@"apply---end");

}


输出结果

<pre>

apply—-begin

1—-{number = 3, name = (null)}

0—-{number = 1, name = main}

2—-{number = 4, name = (null)}

3—-{number = 5, name = (null)}

4—-{number = 3, name = (null)}

5—-{number = 1, name = main}

apply—-end

</pre>

结论

因为是在并发队列中异步执行任务,所以各个任务的执行时间 长短不定,最后顺序也不定,但是`apply_end`一定在最后执行。这是因为`dispatch_apply`函数会等待全部任务执行完毕。

>综上所述,`dispatch_apply`适用于,当前需要等待某堆任务(可以是并发任务队列)完成后才能继续操作的情况

####5.GCD 的队列组:dispatch_group

有时候我们会有这样的需求:分别异步执行2个耗时任务,然后当2个任务都执行完再回到主线程,这时就要用到 GCD 的队列组。

* 要调用队列组的`dispatch_group_async`先把任务放到队列中,然后将队列放入队列组中。或者使用队列组的 `dispatch_group_enter`、`dispatch_group_leave`组合 来实现`dispatch_group_async`.

* 调用队列组的`dispatch_group_notify`回到指定线程执行任务。或者使用`dispatch_group_wait`回到当前线程继续向下执行(会阻塞当前线程)

#####5.1 dipatch\_group\_notify

* 监听 group 中任务的完成状态,当所有的任务都执行完成后,追加任务到group中,并执行任务

/**

  • 队列组 dispatch_group_notify

*/

  • (void)groupNotify {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

    NSLog(@"group---begin");

dispatch_group_t group =  dispatch_group_create();



dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的异步任务1、任务2都执行完毕后,回到主线程执行下边任务

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

    NSLog(@"group---end");

});

}


输出结果:

<pre>

currentThread—-{number = 1, name = main}

group—-begin

1—-{number = 4, name = (null)}

2—-{number = 3, name = (null)}

2—-{number = 3, name = (null)}

1—-{number = 4, name = (null)}

3—-{number = 1, name = main}

3—-{number = 1, name = main}

group—-end

</pre>

结论:

当所有任务都执行完成之后才执行`dispatch_group_notify`block中的任务

####5.2 dispatch\_group\_wait

* 暂停当前线程(阻塞当前线程),等待指定的group 中的任务执行完成后,才会继续执行

/**

  • 队列组 dispatch_group_wait

*/

  • (void)groupWait {

    NSLog(@"currentThread---%@",[NSThread currentThread]); // 打印当前线程

    NSLog(@"group---begin");

dispatch_group_t group =  dispatch_group_create();



dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

});



// 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)

dispatch_group_wait(group, DISPATCH_TIME_FOREVER);



NSLog(@"group---end");

}


输出结果:

<pre>

currentThread—-{number = 1, name = main}

group—-begin

2—-{number = 4, name = (null)}

1—-{number = 3, name = (null)}

2—-{number = 4, name = (null)}

1—-{number = 3, name = (null)}

group—-end

</pre>

结论:

dispatch\_group\_wait会等待所有group任务完成后才继续执行

####5.3 dispatch\_group\_enter、dispatch\_group\_leave

* `dispatch_group_enter`标志着一个任务追加到group,执行一次,相当于group中未执行完毕任务数+1

* `dispatch_group_leave`标志着一个任务离开group,执行一次,相当于group中未执行完毕任务数-1

* 当 group 中未执行完毕任务数为0时,才会拿`dispatch_group_wait`解除阻塞,以及执行追加到`dispatch_group_notify`中的任务

/**

  • 队列组 dispatch_group_enter、dispatch_group_leave

*/

  • (void)groupEnterAndLeave

{

NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程

NSLog(@"group---begin");



dispatch_group_t group = dispatch_group_create();

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_group_enter(group);

dispatch_async(queue, ^{

    // 追加任务1

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程

    }

    dispatch_group_leave(group);

});



dispatch_group_enter(group);

dispatch_async(queue, ^{

    // 追加任务2

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程

    }

    dispatch_group_leave(group);

});



dispatch_group_notify(group, dispatch_get_main_queue(), ^{

    // 等前面的异步操作都执行完毕后,回到主线程.

    for (int i = 0; i < 2; ++i) {

        [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

        NSLog(@"3---%@",[NSThread currentThread]);      // 打印当前线程

    }

    NSLog(@"group---end");

});

// // 等待上面的任务全部完成后,会往下继续执行(会阻塞当前线程)

// dispatch_group_wait(group, DISPATCH_TIME_FOREVER);

//

// NSLog(@"group---end");

}


输出结果

<pre>

currentThread—-{number = 1, name = main}

group—-begin

1—-{number = 4, name = (null)}

2—-{number = 3, name = (null)}

1—-{number = 4, name = (null)}

2—-{number = 3, name = (null)}

3—-{number = 1, name = main}

3—-{number = 1, name = main}

group—-end

</pre>

结论

当所有任务执行完成之后,才执行 `dispatch_group_notify`中的任务,这里 `dispatch_group_enter`、`dispatch_group_leave`组合,其实等同于`dispatch_group_async`.

####6.GCD 信号量:dispatch_semaphore

GCD 中的信号量是指 Dispatch Semaphore,是持有计数的信号。类似于高速路收费站的栏杆。可以通过时,打开栏杆,不可以通过时,关闭栏杆。在Dispatch_Semaphore 中,使用计数来完成这个功能,计数为0时等待,不可通过。计数为1或者大于1时,计数减1且不等待,可通过。

Dispatch Semaphore 提供了三个函数

  * `dispatch_semaphore_create`:创建一个Semaphore并初始化信号的总量

  * `dispatch_semaphore_signal`:发送一个信号,让信号问题加1

  * `dispatch_semaphore_wait`:可以使用总信号量减1,当信号总量为0时就会一直等待(阻塞所有线程),否则就可以正常执行

>注意:信号量的使用前提是:想清楚你需要处理哪个线程等待(阻塞),又要哪个线程继续执行,然后使用信号量

Dispatch Semaphore 在实际开发中主要用于:

1. 保持线程同步,将异步执行任务转换成同步执行任务

2. 保证线程安全,为线程加锁

####6.1 Dispatch Semaphore 线程同步

当异步执行耗时任务,并使用异步执行的结果进行一些额外的操作时,我们要用到这个。换句话说,相当于,将异步执行任务转换成同步执行任务.比如:AFNetworking中AFURLSessionManager.m 里面的 `tasksForKeyPath:`方法。通过引入信号量的方式,等待异步执行任务结果,获取到tasks,然后再返回该tasks。

  • (NSArray *)tasksForKeyPath:(NSString *)keyPath {

    __block NSArray *tasks = nil;

    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    [self.session getTasksWithCompletionHandler:^(NSArray *dataTasks, NSArray *uploadTasks, NSArray *downloadTasks) {

      if ([keyPath isEqualToString:NSStringFromSelector(@selector(dataTasks))]) {
    
          tasks = dataTasks;
    
      } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(uploadTasks))]) {
    
          tasks = uploadTasks;
    
      } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(downloadTasks))]) {
    
          tasks = downloadTasks;
    
      } else if ([keyPath isEqualToString:NSStringFromSelector(@selector(tasks))]) {
    
          tasks = [@[dataTasks, uploadTasks, downloadTasks] valueForKeyPath:@"@unionOfArrays.self"];
    
      }
    
      dispatch_semaphore_signal(semaphore);
    

    }];

    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

    return tasks;

}


下面,我们来利用 Dispatch Semaphore 实现线程同步,将异步任务转换为同步执行任务

/**

  • semaphore 线程同步

*/

  • (void)semaphoreSync {
NSLog(@"currentThread---%@",[NSThread currentThread]);  // 打印当前线程

NSLog(@"semaphore---begin");



dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);



__block int number = 0;

dispatch_async(queue, ^{

    // 追加任务1

    [NSThread sleepForTimeInterval:2];              // 模拟耗时操作

    NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程



    number = 100;



    dispatch_semaphore_signal(semaphore);

});



dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);

NSLog(@"semaphore---end,number = %zd",number);

}


输出结果:

<pre>

currentThread—-{number = 1, name = main}

semaphore—-begin

1—-{number = 3, name = (null)}

semaphore—-end,number = 100

</pre>

结论:

* `semaphore---end`是在执行完 `number = 100;`之后才打印的。而且输出结果 number 为100;

这是因为`异步执行`不会做任务等待,可以继续执行任务,`异步执行`将任务1追加到队列之后,不做等待,接着执行 `dispatch_semaphore_wait`方法。此时 semaphore == 0,当前线程进入等待状态。然后异步任务1开始执行。任务1执行到`dispatch_semaphore_signal`之后,总信号量,此时为1,`dispatch_semaphore_wait`方法使用总信号量减1,正在被阻塞的线程恢复继续执行。

####6.2 Dispatch Semaphore 线程安全和线程同步(为线程加锁)

最后的学习总结:

  1. GCD会自动管理线程生命周期
  2. GCD会充分使用CPU内核
  3. GCD中同步线程不能创建新线程,只能使用当前线程,故无论是串行还是并发都是串行执行任务
  4. GCD中异步线程可以创建新线程,并发队列会根据实际情况创建1条或者多条线程并发执行执行,串行队列只开辟一条新线程并且新线程中串行执行任务
  5. 主线程为特殊同步线程,主队列是串行队列且只能在主线程中串行执行,不可创建新的主线程,故在主线程中通过同步添加任务到主队列中会形成相互等的死锁状态
  6. dispatch_barrier_async 栏栅,通过dispatch_barrier_async或隔开队列执行 无论dispatch_barrier_async前是串行还是并发队列,在队列任务全部执行完之前 会一直等待,完成后执行dispatch_barrier_async中的代码块,执行完后才会继续往下执行
  7. dispatch_group_async将任务添加到队列后再把队列添加到group里 当group执行完会执行dispatch_group_notify里面的代码块
  8. dispatch_group_async也可以在添加完任务后dispatch_group_wait(group, DISPATCH_TIME_FOREVER);阻塞线程来达到执行完group后才执行其他代码的目的
  9. dispatch_group_async也可以用 dispatch_group_enter(追加group+1)、dispatch_group_leave(group-1),但一定要配套使用
  10. dispatch_semaphore dispatch_semaphore_create(创建)、dispatch_semaphore_signal(发送信号、发送后dispatch_semaphore_wait会进行判断是否能解除线程阻塞)、dispatch_semaphore_wait(判断信号是否为0,为0时线程会一直等待阻塞线程直接信号量变为0之后)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342