GCD 系列知识总结

image.png

引言: 越是细节越能体现一个人的严谨,越是微小越是能看到事物的光芒

1. 队列

官方解释:
DispathQueue是FIFO队列,应用程序可以以块对象的形式向其提交任务。调度队列可以串行执行任务,也可以并发执行任务。提交给DispathQueue的工作在系统管理的线程池上执行,不用开发者操心在那个线程上。除了App主线程的调度队列mainQueue外,别的DispathQueue系统不保证它执行任务的时候一直在一个具体的线程中。

您可以同步或异步地安排工作项。当您同步地计划一个工作项时,代码将等待该项完成执行。当您异步地计划工作项时,代码在工作项运行到其他地方时继续执行。

避免创建过多线程
如果使用DispathQueue去并发执行任务时,不要再执行的时候阻塞任务执行的线程。如果阻塞了,系统会再额外创建线程去执行别的任务,如果任务快太多,系统可能会耗尽应用程序的线程。

应用程序消耗太多线程的另一种方式是创建太多私有并发调度队列。因为每个调度队列都消耗线程资源,所以创建额外的并发调度队列会加剧线程消耗问题。不要创建私有并发队列,而是将任务提交到一个全局global并发调度队列。对于串行任务,请将串行队列的目标设置为全局并发队列之一。这样,您可以保持队列的序列化行为,同时最小化创建线程的独立队列的数量。

dispatch_queue_t dySerial = dispatch_queue_create("自己创建串行队列", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
dispatch_queue_t dyConcurrent = dispatch_queue_create("自己创建并行队列", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列

dispatch_queue_t osMainSerial = dispatch_get_main_queue(); //系统默认主队列,就是主线程
dispatch_queue_t osConcurrent = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //系统默认的全局并行队列

2. 执行

    //异步执行
    dispatch_async("传入队列", 任务block);
    
    //同步执行
    dispatch_sync("传入队列", 任务block);

3. 不同执行 + 不同队列效果

3.1 同步执行 + 并行队列 不会创建多线程 顺序执行
    //当前线程打印标记
    NSLog(@"打印当前线程---%@",[NSThread currentThread]);  // 打印当前线程

    NSLog(@"*********同步执行 + 并行队列 不会创建多线程 顺序执行*********");
    dispatch_sync(dyConcurrent, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务1线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(dyConcurrent, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(dyConcurrent, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务3线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
2018-02-26 15:58:11.853843+0800 GCD 系列知识点[47527:3342566] 打印当前线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.854101+0800 GCD 系列知识点[47527:3342566] *********同步执行 + 并行队列 不会创建多线程 顺序执行*********
2018-02-26 15:58:11.854512+0800 GCD 系列知识点[47527:3342566] 任务1线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.855069+0800 GCD 系列知识点[47527:3342566] 任务1线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.855570+0800 GCD 系列知识点[47527:3342566] 任务2线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.856073+0800 GCD 系列知识点[47527:3342566] 任务2线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.856731+0800 GCD 系列知识点[47527:3342566] 任务3线程---<NSThread: 0x600000069600>{number = 1, name = main}
2018-02-26 15:58:11.857079+0800 GCD 系列知识点[47527:3342566] 任务3线程---<NSThread: 0x600000069600>{number = 1, name = main}

3.2 异步执行 + 并发队列 会创建多个线程
    NSLog(@"*********异步执行 + 并发队列 会创建多个线程 不是顺序执行*********");
    dispatch_async(dyConcurrent, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务1线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(dyConcurrent, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(dyConcurrent, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务3线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
2018-02-26 15:59:55.983225+0800 GCD 系列知识点[47573:3349914] 打印当前线程---<NSThread: 0x600000076140>{number = 1, name = main}
2018-02-26 15:59:55.983441+0800 GCD 系列知识点[47573:3349914] *********异步执行 + 并发队列 会创建多个线程 不是顺序执行*********
2018-02-26 15:59:55.983760+0800 GCD 系列知识点[47573:3350179] 任务2线程---<NSThread: 0x604000679b80>{number = 5, name = (null)}
2018-02-26 15:59:55.983795+0800 GCD 系列知识点[47573:3350180] 任务1线程---<NSThread: 0x6000002798c0>{number = 4, name = (null)}
2018-02-26 15:59:55.983821+0800 GCD 系列知识点[47573:3350190] 任务3线程---<NSThread: 0x60000027a240>{number = 6, name = (null)}
2018-02-26 15:59:55.984116+0800 GCD 系列知识点[47573:3350179] 任务2线程---<NSThread: 0x604000679b80>{number = 5, name = (null)}
2018-02-26 15:59:55.984126+0800 GCD 系列知识点[47573:3350180] 任务1线程---<NSThread: 0x6000002798c0>{number = 4, name = (null)}
2018-02-26 15:59:55.984190+0800 GCD 系列知识点[47573:3350190] 任务3线程---<NSThread: 0x60000027a240>{number = 6, name = (null)}
3.3 同步执行 + 串行队列 不会创建新线程
    NSLog(@"*********同步执行 + 串行队列 不会创建新线程*********");
    dispatch_sync(dySerial, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务1线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_sync(dySerial, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
2018-02-26 16:02:43.429624+0800 GCD 系列知识点[47637:3363265] 打印当前线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.429817+0800 GCD 系列知识点[47637:3363265] *********同步执行 + 串行队列 不会创建新线程*********
2018-02-26 16:02:43.430040+0800 GCD 系列知识点[47637:3363265] 任务1线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430238+0800 GCD 系列知识点[47637:3363265] 任务1线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430346+0800 GCD 系列知识点[47637:3363265] 任务2线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
2018-02-26 16:02:43.430466+0800 GCD 系列知识点[47637:3363265] 任务2线程---<NSThread: 0x604000078bc0>{number = 1, name = main}
3.4 异步执行 + 串行队列 只创建了一个线程(区别于当前线程的新线程)
    NSLog(@"*********异步执行 + 串行队列 只创建了1个线程*********");
    dispatch_async(dySerial, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务1线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(dySerial, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
2018-02-26 16:04:52.596042+0800 GCD 系列知识点[47689:3374647] 打印当前线程---<NSThread: 0x60000006f000>{number = 1, name = main}
2018-02-26 16:04:52.596243+0800 GCD 系列知识点[47689:3374647] *********异步执行 + 串行队列 只创建了1个线程*********
2018-02-26 16:04:52.596596+0800 GCD 系列知识点[47689:3375109] 任务1线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.596912+0800 GCD 系列知识点[47689:3375109] 任务1线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.597323+0800 GCD 系列知识点[47689:3375109] 任务2线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
2018-02-26 16:04:52.597453+0800 GCD 系列知识点[47689:3375109] 任务2线程---<NSThread: 0x60000046ab00>{number = 4, name = (null)}
3.5 同步/异步 + 主队列dispatch_get_main_queue()执行效果
3.5.1 特别注意: 同步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 会出现死锁
    NSLog(@"*********特别注意 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 会出现死锁 *********");
    NSLog(@"任务1");
    dispatch_sync(osMainSerial, ^{
        NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"任务3");
Xcode运行 项目直接崩溃

首先执行任务1,然后遇到dispatch_sync 同步线程,当前线程进入等待,等待同步线程中的任务2执行完再执行任务3,这个任务2是加入到mainQueue主队列中。
dispatch_sync(osMainSerial,^(void)()) 是同步一个任务到主队列中,而当前去同步的正是主队列。因为队列的执行是FIFO(先进先出),所以两个都在等待对方完成,就会造成死锁。


image.png
3.5.2 如果这个操作放在非主队列中执行就不会有问题
//但是如果dispatch_sync(osMainSerial,^void()) 这个任务的执行是放在 非主线程中(其他子线程) 执行的,就没问题
//NSThread detachNewThread 开辟一个新线程执行方法
[NSThread detachNewThreadSelector:@selector(testOtherThreadGetMainSyncSomething) toTarget:self withObject:nil];

//测试 同步执行 + 主队列dispatch_get_main_queue() 放在其他线程中
- (void)testOtherThreadGetMainSyncSomething{
    dispatch_queue_t osMainSerial = dispatch_get_main_queue(); //系统默认主队列,就是主线程
    NSLog(@"任务1");
    dispatch_sync(osMainSerial, ^{
        NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
    });
    NSLog(@"任务3");
}
2018-02-26 17:58:01.616851+0800 GCD 系列知识点[50280:3588248] 打印当前线程---<NSThread: 0x60400006aac0>{number = 1, name = main}
2018-02-26 17:58:01.617322+0800 GCD 系列知识点[50280:3588554] 任务1
2018-02-26 17:58:01.623797+0800 GCD 系列知识点[50280:3588248] 任务2线程---<NSThread: 0x60400006aac0>{number = 1, name = main}
2018-02-26 17:58:01.624153+0800 GCD 系列知识点[50280:3588554] 任务3
3.6 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 异步执行的时候 不会创建新线程,只会使用当前线程
NSLog(@"*********特别注意 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 异步执行的时候 不会创建新线程*********");
    dispatch_async(osMainSerial, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务1线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
    dispatch_async(osMainSerial, ^{
        for (int i = 0; i < 2; i++){
            NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
        }
    });
2018-02-26 17:52:39.747531+0800 GCD 系列知识点[50190:3564074] 打印当前线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.747691+0800 GCD 系列知识点[50190:3564074] *********特别注意 异步执行 + 对于特殊的串行队列 dispatch_get_main_queue() 异步执行的时候 不会创建新线程*********
2018-02-26 17:52:39.773217+0800 GCD 系列知识点[50190:3564074] 任务1线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.773976+0800 GCD 系列知识点[50190:3564074] 任务1线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.775269+0800 GCD 系列知识点[50190:3564074] 任务2线程---<NSThread: 0x60000006bf80>{number = 1, name = main}
2018-02-26 17:52:39.776235+0800 GCD 系列知识点[50190:3564074] 任务2线程---<NSThread: 0x60000006bf80>{number = 1, name = main}

4. 关于死锁

4.1 在上3.5.1上介绍 同步执行(dispatch_sync ) + 主队列 = 死锁。那么如果同步执行的 + 非主队列 是否就一定不会出现死锁了?看下面的代码:
//测试 同步 + 串行队列(非组队列) 死锁
[self testAsyncSerialLockSometing];

//测试死锁 同步执行 + 串行队列(非主队列)死锁的情况

/**
 用异步执行一个串行队列的到一个新的线程,然后再同步执行这个串行队列,会发生死锁
 */
- (void)testAsyncSerialLockSometing{
    
    //这里自己创建一个串行队列new dySerial
    dispatch_queue_t dySerial = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
    
    //先异步执行这个串行队列,这样可以得到一个子线程。这个串行队列现在是挂载到
    dispatch_async(dySerial, ^{
        NSLog(@"任务1线程---%@",[NSThread currentThread]);      // 打印当前线程
        
        //这里再同步执行这个串行队列
        dispatch_sync(dySerial, ^{
            NSLog(@"任务2线程---%@",[NSThread currentThread]);      // 打印当前线程
        });
        
        NSLog(@"任务3线程---%@",[NSThread currentThread]);      // 打印当前线程
    });
}

2018-02-27 09:57:49.336615+0800 GCD 系列知识点[55106:3863926] 打印当前线程---<NSThread: 0x60400006efc0>{number = 1, name = main}
2018-02-27 09:57:49.336972+0800 GCD 系列知识点[55106:3864092] 任务1线程---<NSThread: 0x60400027ca80>{number = 3, name = (null)}
(lldb) 

Thread 3: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

会发现还是会出现死锁的情况,综上所述不难发现这种死锁产生的原因:在串行队列A(主队列或自己创建的串行队列)的线程中同步执行一个任务,而且这个任务还是放在串行队列A中。这样就会出现死锁。

4.2 还有一种死锁,就是当前的串行队列被阻塞,然后同步任务到这个队列就会出现
//测试 主线程被阻塞 死锁
[self testThreadBusyLockSomething];

- (void)testThreadBusyLockSomething{
    
    dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
    dispatch_async(dyConcurrent, ^{
        NSLog(@"任务1");
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"任务3");
        });
        NSLog(@"任务4");
    });
    NSLog(@"任务2");

    //这里有个死循环
    while(1){
        
    }
}
2018-02-27 10:38:31.763153+0800 GCD 系列知识点[55527:3978731] 打印当前线程---<NSThread: 0x604000065500>{number = 1, name = main}
2018-02-27 10:38:31.763335+0800 GCD 系列知识点[55527:3978731] 任务2
2018-02-27 10:38:31.763358+0800 GCD 系列知识点[55527:3978980] 任务1

执行顺序:


image.png

main队列中死循环不结束,任务3不执行,任务3又是global全局队列同步过去的,global中同步的任务3不执行完、任务4是不会执行的。

了解死锁的发生时机后就很容易理解,所谓队列(串行并行)就是一个顺序执行的甬道。所谓同步异步就是决定执行这个队列的时候是否开辟新的线程去执行。

4. 关于GCD之间的通信,就是一个概念记住就好。不同的队列直接相互通信,只需要在执行任务的时候同步或异步执行任务的时候调用别的队列传值,最常见的就是

    // 获取全局并发队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 
    // 获取主队列
    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    
    dispatch_async(queue, ^{
        // 异步追加任务
        for (int i = 0; i < 2; ++i) {
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"1---%@",[NSThread currentThread]);      // 打印当前线程
        }
        
        // 回到主线程
        dispatch_async(mainQueue, ^{
            // 追加在主线程中执行的任务
            [NSThread sleepForTimeInterval:2];              // 模拟耗时操作
            NSLog(@"2---%@",[NSThread currentThread]);      // 打印当前线程
        });
    });

5. GCD常用方法

5.1 组操作dispatch_group
5.1.1 dispatch_group_notify、dispatch_group_wait 基本用法

异步执行多个耗时任务,然后多个任务都执行完毕后再回到主线程执行任务。这时候我们可以用到 GCD 的队列组。

//用于创建任务组
dispatch_group_t dispatch_group_create(void);

//异步任务提交到指定任务组和指定下拿出队列执行
dispatch_group_async(dispatch_group_t group,
                          dispatch_queue_t queue,
                          dispatch_block_t block);

//多个异步任务完成后调用block(不会阻塞当前线程)
dispatch_group_notify(dispatch_group_t group,
                           dispatch_queue_t queue, 
                           dispatch_block_t block);
//多个异步任务完成后dispatch_group_wait这句话后面的代码才会执行(会阻塞当前线程,任务全部执行完毕或超时才会接触阻塞)
//注意:因为dispatch_group_wait会阻塞当前线程,所以一般情况不要放入主线程
dispatch_group_wait(dispatch_group_t group, 
                         dispatch_time_t timeout);

//使用的时候成对使用,在执行某个任务前调用enter,待执行任务数+1
//在某个任务完成后,调用leave,待执行任务数-1
//只有所有任务都完成(任务数为0)才会触发dispatch_group_notify 或  dispatch_group_wait
dispatch_group_enter(dispatch_group_t group);
dispatch_group_leave(dispatch_group_t group);

看Demo

-(void)testGroup{
    
    dispatch_group_t group = dispatch_group_create(); //创建zhu
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing1");
    }) ;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing2");
    }) ;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing3");
        sleep(3);
    }) ;
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"接着做某事");
}
2018-02-28 16:16:29.172002+0800 GCD 系列知识点[14525:1509846] doSomeThing1
2018-02-28 16:16:29.172004+0800 GCD 系列知识点[14525:1509848] doSomeThing2
2018-02-28 16:16:29.172014+0800 GCD 系列知识点[14525:1509849] doSomeThing3
2018-02-28 16:16:32.174314+0800 GCD 系列知识点[14525:1509719] 接着做某事
2018-02-28 16:16:32.179732+0800 GCD 系列知识点[14525:1509719] 刷新界面

运行发现 ,所有任务都执行完毕了,NSLog(@"刷新界面");这句代码才执行,NSLog(@"接着做某事");这句代码才执行。
注意
1,dispatch_group_wait会阻塞当前线程,什么时候在它之前group_async执行的block任务结束了,或者wait自己设置的时间超时了,代码才会往下执行。如果dispatch_group_wait之前没有group_async block任务,它就会马上执行
2,dispatch_group_waitdispatch_group_notify 同时出现,只有dispatch_group_wait执行后, dispatch_group_notify才会执行,不管dispatch_group_waitdispatch_group_notify之前还是之后。
dispatch_group_wait会阻塞当前线程,所以不要放在App主线程。

5.1.2 dispatch_group_enter、dispatch_group_leave 基本用法

在介绍这个用法之前,我们先看一个例子

-(void)testGroup{
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(2);
            NSLog(@"doSomeThing1");
        });
        
    }) ;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing2");
    }) ;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing3");
    }) ;
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}
2018-02-28 17:26:07.752043+0800 GCD 系列知识点[15102:1596389] doSomeThing2
2018-02-28 17:26:07.752051+0800 GCD 系列知识点[15102:1596391] doSomeThing3
2018-02-28 17:26:07.761563+0800 GCD 系列知识点[15102:1596119] 刷新界面
2018-02-28 17:26:09.756480+0800 GCD 系列知识点[15102:1596390] doSomeThing1

会发现doSomeThing1还没执行完就进入dispatch_group_notify 打印刷新页面,这是为什么了?
看代码会发现 doSomeThing1是放在了一个异步线程中执行的。但是dispatch_group_notify 只会监听dispatch_group_async中执行的同步任务。如果dispatch_group_async执行的是个异步任务,那notify 监听结果可能不是你想要的。
那我们能不能实现dispatch_group_async执行多异步任务,同时还能监听到任务全部完成了?做到这些就需要dispatch_group_enterdispatch_group_leave了,看下面例子:

-(void)testGroup{
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_enter(group);
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            sleep(2);
            NSLog(@"doSomeThing1");
            dispatch_group_leave(group);
        });
        
    }) ;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing2");
    }) ;
    dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSLog(@"doSomeThing3");
    }) ;
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"刷新界面");
    });
}
2018-02-28 17:34:10.449511+0800 GCD 系列知识点[15199:1621095] doSomeThing3
2018-02-28 17:34:10.449512+0800 GCD 系列知识点[15199:1621093] doSomeThing2
2018-02-28 17:34:12.449752+0800 GCD 系列知识点[15199:1621094] doSomeThing1
2018-02-28 17:34:12.450328+0800 GCD 系列知识点[15199:1620866] 刷新界面

dispatch_group_enter 用来标记这个任务的开始,dispatch_group_leave用来标记任务真正完成。这样成对标记可以实现标记dispatch_group_async任务的真正执行完毕,dispatch_group_notify也就能监听到了。

记住:
1. dispatch_group_enter 和 dispatch_group_leave 是成对出现的,不然dispatch_group 队列会出错。

2. dispatch_group_enter调用n次,就需要dispatch_group_leave调用n次,这样dispatch_group_notify才会执行

3. dispatch_group_enter/dispatch_group_leave 可以在mainQuene上用,不会像Semaphore卡住主线程

5.1.3 用dispatch_group 处理多网络请求都返回在刷新页面

根据5.1.2 我们可以在实际开发中运用dispatch_group 来处理多个网络请求都完成的通知。因为我们的网络请求都是异步的,正好可以用dispatch_group_enter 和 dispatch_group_leave实现标记完成。

下面写一段伪代码展示:

dispatch_group_t group = dispatch_group_create();

//网络请求1
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [异步网络请求1:{
          成功Block:dispatch_group_leave(group);
          失败Block:dispatch_group_leave(group);
       }];
    })

//网络请求2
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [异步网络请求3:{
          成功Block:dispatch_group_leave(group);
          失败Block:dispatch_group_leave(group);
       }];
    })

//网络请求3
dispatch_group_enter(group);
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       [异步网络请求3:{
          成功Block:dispatch_group_leave(group);
          失败Block:dispatch_group_leave(group);
       }];
    })

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有网络请求完毕");
    });

这样就可以处理多网络请求同时完成的情况了。

5.2 只执行一次dispatch_once

我们在创建单例、或者有整个程序运行过程中只执行一次的代码时,我们就用到了 GCD 的 dispatch_once 函数。使用
dispatch_once 函数能保证某段代码在程序运行过程中只被执行1次,并且即使在多线程的环境下,dispatch_once也可以保证线程安全。

- (void)test {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        // 只执行1次的代码(这里面默认是线程安全的)
    });
}
5.3 延时执行方法:dispatch_after

在指定时间(例如3秒)之后执行某个任务。可以用 GCD 的dispatch_after函数来实现。

dispatch_after函数并不是在指定时间之后才开始执行处理,而是在指定时间之后将任务追加到主队列中。严格来说,这个时间并不是绝对准确的,但想要大致延迟执行任务,dispatch_after函数是很有效的

- (void)after {    
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        // 2.0秒后异步追加任务代码到主队列,并开始执行
        NSLog(@"after---%@",[NSThread currentThread]);  // 打印当前线程
    });
}
5.4 栅栏方法dispatch_barrier_async

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

如下,如果想

-(void)testBarrier{
    
    dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
    
    dispatch_group_t group = dispatch_group_create();
    
    dispatch_group_async(group, dyConcurrent, ^{
        for (int i = 0; i< 3; i++) {
            NSLog(@"任务1");
        }
    });
    
    dispatch_group_async(group, dyConcurrent, ^{
        for (int i = 0; i< 3; i++) {
            NSLog(@"任务2");
        }
    });
    
    dispatch_barrier_async(dyConcurrent, ^{
        
        NSLog(@"追加追加追加");
    });
    
    dispatch_group_async(group, dyConcurrent, ^{
        for (int i = 0; i< 3; i++) {
            NSLog(@"任务3");
        }
    });
    
    dispatch_group_async(group, dyConcurrent, ^{
        for (int i = 0; i< 3; i++) {
            NSLog(@"任务4");
        }
    });
}
2018-03-01 09:16:35.722991+0800 GCD 系列知识点[16166:1788483] 打印当前线程---<NSThread: 0x60400007f880>{number = 1, name = main}
2018-03-01 09:16:35.723276+0800 GCD 系列知识点[16166:1788561] 任务1
2018-03-01 09:16:35.723288+0800 GCD 系列知识点[16166:1788563] 任务2
2018-03-01 09:16:35.723442+0800 GCD 系列知识点[16166:1788561] 任务1
2018-03-01 09:16:35.723452+0800 GCD 系列知识点[16166:1788563] 任务2
2018-03-01 09:16:35.723532+0800 GCD 系列知识点[16166:1788561] 任务1
2018-03-01 09:16:35.723549+0800 GCD 系列知识点[16166:1788563] 任务2
2018-03-01 09:16:35.723785+0800 GCD 系列知识点[16166:1788563] 追加追加追加
2018-03-01 09:16:35.724089+0800 GCD 系列知识点[16166:1788560] 任务3
2018-03-01 09:16:35.724357+0800 GCD 系列知识点[16166:1788563] 任务4
2018-03-01 09:16:35.725130+0800 GCD 系列知识点[16166:1788560] 任务3
2018-03-01 09:16:35.725511+0800 GCD 系列知识点[16166:1788563] 任务4
2018-03-01 09:16:35.725511+0800 GCD 系列知识点[16166:1788560] 任务3
2018-03-01 09:16:35.725900+0800 GCD 系列知识点[16166:1788563] 任务4

会发现,任务1和任务2 全部执行完毕后 在执行追加任务,然后再执行任务3和任务4,如果我们下面代码注释掉再运行一下

/*
dispatch_barrier_async(dyConcurrent, ^{
        NSLog(@"追加追加追加");
    });
*/
2018-03-01 09:19:36.735062+0800 GCD 系列知识点[16214:1797692] 任务1
2018-03-01 09:19:36.735062+0800 GCD 系列知识点[16214:1797691] 任务2
2018-03-01 09:19:36.735089+0800 GCD 系列知识点[16214:1797705] 任务3
2018-03-01 09:19:36.735116+0800 GCD 系列知识点[16214:1797693] 任务4
2018-03-01 09:19:36.735310+0800 GCD 系列知识点[16214:1797691] 任务2
2018-03-01 09:19:36.735315+0800 GCD 系列知识点[16214:1797692] 任务1
2018-03-01 09:19:36.735436+0800 GCD 系列知识点[16214:1797705] 任务3
2018-03-01 09:19:36.735623+0800 GCD 系列知识点[16214:1797693] 任务4
2018-03-01 09:19:36.735653+0800 GCD 系列知识点[16214:1797691] 任务2
2018-03-01 09:19:36.735716+0800 GCD 系列知识点[16214:1797692] 任务1
2018-03-01 09:19:36.736728+0800 GCD 系列知识点[16214:1797705] 任务3
2018-03-01 09:19:36.738247+0800 GCD 系列知识点[16214:1797693] 任务4

会发现各个任务是混合异步执行的。
通过这个例子我们能明白dispatch_barrier_async主要是分割异步任务的作用。

5.5 dispatch_apply 快速遍历
//参数iterations遍历次数,queue执行遍历任务的队列,block每次遍历回调
dispatch_apply(size_t iterations, dispatch_queue_t queue,
        DISPATCH_NOESCAPE void (^block)(size_t));

和一般我们用for循环遍历不同的是,如果dispatch_apply调用的时候传入queue的是并发队列,那么它的遍历就是异步执行的,会在多个线程中进行,而我们直接for循环都是在当前线程中进行的。如下

-(void)testApply{
    
    NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
    
//    dispatch_queue_t queue = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(imageArray.count, queue, ^(size_t index) {
        NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
    });
}
018-03-01 09:49:13.824457+0800 GCD 系列知识点[16580:1890506] image1---<NSThread: 0x600000064e80>{number = 1, name = main}
2018-03-01 09:49:13.824554+0800 GCD 系列知识点[16580:1890719] image3---<NSThread: 0x60400047be00>{number = 5, name = (null)}
2018-03-01 09:49:13.824555+0800 GCD 系列知识点[16580:1890718] image2---<NSThread: 0x600000265780>{number = 4, name = (null)}
2018-03-01 09:49:13.824601+0800 GCD 系列知识点[16580:1890721] image4---<NSThread: 0x6040004647c0>{number = 3, name = (null)}
2018-03-01 09:49:13.824706+0800 GCD 系列知识点[16580:1890506] image5---<NSThread: 0x600000064e80>{number = 1, name = main}
2018-03-01 09:49:13.824805+0800 GCD 系列知识点[16580:1890719] image6---<NSThread: 0x60400047be00>{number = 5, name = (null)}
2018-03-01 09:49:13.824987+0800 GCD 系列知识点[16580:1890721] image8---<NSThread: 0x6040004647c0>{number = 3, name = (null)}
2018-03-01 09:49:13.824991+0800 GCD 系列知识点[16580:1890718] image7---<NSThread: 0x600000265780>{number = 4, name = (null)}

会发现打印的线程号都是不同的。但是如果传入的是个串行队列(非主队列)它就和普通的for循环没什么不同了。如下

-(void)testApply{
    
    NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
    
    dispatch_queue_t dySerial = dispatch_queue_create("new dySerial", DISPATCH_QUEUE_SERIAL); //自己创建串行队列
//    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    dispatch_apply(imageArray.count, dySerial, ^(size_t index) {
        NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
    });
}
2018-03-01 09:42:20.361535+0800 GCD 系列知识点[16487:1861258] image1---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.361714+0800 GCD 系列知识点[16487:1861258] image2---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.361844+0800 GCD 系列知识点[16487:1861258] image3---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362145+0800 GCD 系列知识点[16487:1861258] image4---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362262+0800 GCD 系列知识点[16487:1861258] image5---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362374+0800 GCD 系列知识点[16487:1861258] image6---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362489+0800 GCD 系列知识点[16487:1861258] image7---<NSThread: 0x60000006e140>{number = 1, name = main}
2018-03-01 09:42:20.362604+0800 GCD 系列知识点[16487:1861258] image8---<NSThread: 0x60000006e140>{number = 1, name = main}

会发现和不同的for循环没什么不同,都是在主线程中进行的。

注意:如果执行dispatch_apply的线程是串行队列A的线程,同时执行dispatch_apply时传入queue的也是串行队列A,会发生死锁。比如直接在主队列中执行dispatch_apply,同时传入queue也是主队列,如:

//直接调用会出现死锁
[self testApply];

-(void)testApply{
    
    NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8"];
    dispatch_queue_t queue = dispatch_get_main_queue();
    
    dispatch_apply(imageArray.count, queue, ^(size_t index) {
        NSLog(@"%@---%@",imageArray[index], [NSThread currentThread]);
    });
}

当然这样做也没什么意义,一般dispatch_apply都是为了高效的异步的遍历数组。所以一般dispatch_apply 传入的queue都是一个并发队列,同时还把dispatch_apply整体方法一个异步执行的并发队列中,如:

    NSArray *array = @[@"1", @"2", @"3", @"4", @"5", @"6"];
    dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
    
    dispatch_async(queue, ^{
        
        dispatch_apply([array count], queue, ^(size_t index) {
            
            NSLog(@"%zu : %@", index, [array objectAtIndex:index]);
        });
        
        dispatch_async(dispatch_get_main_queue(), ^{
            NSLog(@"currentThread = %@", [NSThread currentThread]);
            NSLog(@"done");
        });
    });
5.6 信号量 dispatch_semaphore

干啥用的了,总结一句话:使用dispatch_semaphore 可以更好的控制并发多线程的任务处理。

//创建一个信号量,给他设置个信号量值
dispatch_semaphore_create(long value);
//可以使总信号量减1,当信号总量为0时就会一直等待,阻塞当前线程(当timeout 时间过后 也可以继续执行),否则就可以正常执行。

dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

//发送一个信号,使信号总量加1
dispatch_semaphore_signal(dispatch_semaphore_t dsema);

看着也看不出来有啥用,下面举例一些常见用法。

5.6.1 控制并发线程数量

下面来看看实际应用的例子:
现在我有一组图片地址,我需要开辟一个并发队列来下载这些图片,等这些图片都下载完了,告诉我我要去搞事情。

- (void)testSemaphoreSomething{
    NSArray *imageArray = @[@"image1",@"image2",@"image3",@"image4",@"image5",@"image6",@"image7",@"image8",@"image8",@"image10"];
    
    dispatch_queue_t dyConcurrent = dispatch_queue_create("dyConcurrent1", DISPATCH_QUEUE_CONCURRENT); //自己创建并行队列
    
//    dispatch_semaphore_t cacheSemphore = dispatch_semaphore_create(5);
    
    for (NSString *oneImage in imageArray){
        dispatch_async(dyConcurrent, ^{
            
//            dispatch_semaphore_wait(cacheSemphore, DISPATCH_TIME_FOREVER);
            
            NSLog(@"下载图片:%@ 线程:%@",oneImage,[NSThread currentThread]);
            sleep(1);
            NSLog(@"下载完成");
            
//            dispatch_semaphore_signal(cacheSemphore);
        });
    }
}

我们直接运行会发现会一下开辟imageArray.count个线程来处理任务。

2018-02-27 18:22:11.014569+0800 GCD 系列知识点[1237:286459] 打印当前线程---<NSThread: 0x604000070200>{number = 1, name = main}
2018-02-27 18:22:11.014969+0800 GCD 系列知识点[1237:286648] 下载图片:image5 线程:<NSThread: 0x604000272240>{number = 7, name = (null)}
2018-02-27 18:22:11.014974+0800 GCD 系列知识点[1237:286651] 下载图片:image2 线程:<NSThread: 0x604000272140>{number = 4, name = (null)}
2018-02-27 18:22:11.015007+0800 GCD 系列知识点[1237:286647] 下载图片:image4 线程:<NSThread: 0x604000272200>{number = 6, name = (null)}
2018-02-27 18:22:11.015007+0800 GCD 系列知识点[1237:286650] 下载图片:image3 线程:<NSThread: 0x600000279080>{number = 5, name = (null)}
2018-02-27 18:22:11.015035+0800 GCD 系列知识点[1237:286649] 下载图片:image1 线程:<NSThread: 0x604000272100>{number = 3, name = (null)}
2018-02-27 18:22:11.015389+0800 GCD 系列知识点[1237:286668] 下载图片:image6 线程:<NSThread: 0x600000279100>{number = 8, name = (null)}
2018-02-27 18:22:11.015492+0800 GCD 系列知识点[1237:286669] 下载图片:image7 线程:<NSThread: 0x600000279180>{number = 9, name = (null)}
2018-02-27 18:22:11.015589+0800 GCD 系列知识点[1237:286670] 下载图片:image8 线程:<NSThread: 0x600000279200>{number = 10, name = (null)}
2018-02-27 18:22:11.015684+0800 GCD 系列知识点[1237:286671] 下载图片:image9 线程:<NSThread: 0x600000279280>{number = 11, name = (null)}
2018-02-27 18:22:11.015764+0800 GCD 系列知识点[1237:286672] 下载图片:image10 线程:<NSThread: 0x600000279300>{number = 12, name = (null)}
2018-02-27 18:22:12.017230+0800 GCD 系列知识点[1237:286651] 下载完成
2018-02-27 18:22:12.017232+0800 GCD 系列知识点[1237:286647] 下载完成
2018-02-27 18:22:12.017249+0800 GCD 系列知识点[1237:286648] 下载完成
2018-02-27 18:22:12.017253+0800 GCD 系列知识点[1237:286650] 下载完成
2018-02-27 18:22:12.017292+0800 GCD 系列知识点[1237:286649] 下载完成
2018-02-27 18:22:12.018566+0800 GCD 系列知识点[1237:286668] 下载完成
2018-02-27 18:22:12.023732+0800 GCD 系列知识点[1237:286671] 下载完成
2018-02-27 18:22:12.023728+0800 GCD 系列知识点[1237:286670] 下载完成
2018-02-27 18:22:12.023732+0800 GCD 系列知识点[1237:286669] 下载完成
2018-02-27 18:22:12.023761+0800 GCD 系列知识点[1237:286672] 下载完成

假如这里imageArray.count是100多,很显然,我们这样的暴力处理是不切合实际的。使用 Semaphore我们可以控制同时并发的线程数量。我们打开屏蔽代码,看输出结果:

2018-02-27 18:25:25.741325+0800 GCD 系列知识点[1265:295325] 打印当前线程---<NSThread: 0x60400006e800>{number = 1, name = main}
2018-02-27 18:25:25.741625+0800 GCD 系列知识点[1265:295509] 下载图片:image2 线程:<NSThread: 0x60000027b380>{number = 4, name = (null)}
2018-02-27 18:25:25.741630+0800 GCD 系列知识点[1265:295508] 下载图片:image1 线程:<NSThread: 0x60400026fac0>{number = 3, name = (null)}
2018-02-27 18:25:25.741637+0800 GCD 系列知识点[1265:295510] 下载图片:image3 线程:<NSThread: 0x60000027b280>{number = 5, name = (null)}
2018-02-27 18:25:25.741677+0800 GCD 系列知识点[1265:295511] 下载图片:image4 线程:<NSThread: 0x60000027b240>{number = 6, name = (null)}
2018-02-27 18:25:25.742019+0800 GCD 系列知识点[1265:295525] 下载图片:image6 线程:<NSThread: 0x60400026fd00>{number = 7, name = (null)}
2018-02-27 18:25:26.742404+0800 GCD 系列知识点[1265:295509] 下载完成
2018-02-27 18:25:26.742442+0800 GCD 系列知识点[1265:295508] 下载完成
2018-02-27 18:25:26.742871+0800 GCD 系列知识点[1265:295525] 下载完成
2018-02-27 18:25:26.742871+0800 GCD 系列知识点[1265:295511] 下载完成
2018-02-27 18:25:26.742907+0800 GCD 系列知识点[1265:295510] 下载完成
2018-02-27 18:25:26.742965+0800 GCD 系列知识点[1265:295524] 下载图片:image5 线程:<NSThread: 0x604000270080>{number = 8, name = (null)}
2018-02-27 18:25:26.743013+0800 GCD 系列知识点[1265:295526] 下载图片:image7 线程:<NSThread: 0x60000027c080>{number = 9, name = (null)}
2018-02-27 18:25:26.743472+0800 GCD 系列知识点[1265:295527] 下载图片:image8 线程:<NSThread: 0x60000027c700>{number = 10, name = (null)}
2018-02-27 18:25:26.743472+0800 GCD 系列知识点[1265:295528] 下载图片:image9 线程:<NSThread: 0x60000027c640>{number = 11, name = (null)}
2018-02-27 18:25:26.743546+0800 GCD 系列知识点[1265:295529] 下载图片:image10 线程:<NSThread: 0x60400026ac80>{number = 12, name = (null)}
2018-02-27 18:25:27.746854+0800 GCD 系列知识点[1265:295528] 下载完成
2018-02-27 18:25:27.746912+0800 GCD 系列知识点[1265:295527] 下载完成
2018-02-27 18:25:27.746855+0800 GCD 系列知识点[1265:295524] 下载完成
2018-02-27 18:25:27.746954+0800 GCD 系列知识点[1265:295526] 下载完成
2018-02-27 18:25:27.746981+0800 GCD 系列知识点[1265:295529] 下载完成

会发现我们先开辟 5 个线程去下载,5个都下载完成了后再开辟5个去搞,这样就可以减轻系统并发的数量。

看结果你是不是感觉这有点像dispatch_barrier_async都是把任务拆成几部分一块一块执行,但是他们是有本质不同的:dispatch_barrier_async控制的是任务,它操作的对象是任务分块,不会处理线程的多少,而Semaphore控制同时并发的线程数量,再由一定的线程数量去执行任务。这好比同样是盖楼要10个人,barrier类似于我把楼分成2部分,我们10个人先盖第一部分,第一部分完了再盖第二部分。而Semaphore是我们同时只让5个人盖楼,剩下的休息,谁干完了谁休息,那个之前在休息的上来干。

那如果dispatch_semaphore_create(5)改成dispatch_semaphore_create(1)会怎么样,改完之后运行。

2018-02-27 18:29:33.880314+0800 GCD 系列知识点[1322:310027] 打印当前线程---<NSThread: 0x604000077200>{number = 1, name = main}
2018-02-27 18:29:33.880623+0800 GCD 系列知识点[1322:310152] 下载图片:image1 线程:<NSThread: 0x604000478c40>{number = 3, name = (null)}
2018-02-27 18:29:34.881307+0800 GCD 系列知识点[1322:310152] 下载完成
2018-02-27 18:29:34.881755+0800 GCD 系列知识点[1322:310155] 下载图片:image3 线程:<NSThread: 0x60400046f280>{number = 4, name = (null)}
2018-02-27 18:29:35.887236+0800 GCD 系列知识点[1322:310155] 下载完成
2018-02-27 18:29:35.887694+0800 GCD 系列知识点[1322:310153] 下载图片:image2 线程:<NSThread: 0x604000479540>{number = 5, name = (null)}
2018-02-27 18:29:36.891046+0800 GCD 系列知识点[1322:310153] 下载完成
2018-02-27 18:29:36.891503+0800 GCD 系列知识点[1322:310154] 下载图片:image4 线程:<NSThread: 0x60000007a380>{number = 6, name = (null)}
2018-02-27 18:29:37.894212+0800 GCD 系列知识点[1322:310154] 下载完成
2018-02-27 18:29:37.894732+0800 GCD 系列知识点[1322:310164] 下载图片:image5 线程:<NSThread: 0x600000263280>{number = 7, name = (null)}
2018-02-27 18:29:38.900181+0800 GCD 系列知识点[1322:310164] 下载完成
2018-02-27 18:29:38.900712+0800 GCD 系列知识点[1322:310166] 下载图片:image7 线程:<NSThread: 0x604000479740>{number = 8, name = (null)}
2018-02-27 18:29:39.904618+0800 GCD 系列知识点[1322:310166] 下载完成
2018-02-27 18:29:39.905013+0800 GCD 系列知识点[1322:310165] 下载图片:image6 线程:<NSThread: 0x600000263580>{number = 9, name = (null)}
2018-02-27 18:29:40.908401+0800 GCD 系列知识点[1322:310165] 下载完成
2018-02-27 18:29:40.908814+0800 GCD 系列知识点[1322:310168] 下载图片:image9 线程:<NSThread: 0x6000002631c0>{number = 10, name = (null)}
2018-02-27 18:29:41.910161+0800 GCD 系列知识点[1322:310168] 下载完成
2018-02-27 18:29:41.910634+0800 GCD 系列知识点[1322:310167] 下载图片:image8 线程:<NSThread: 0x6040004794c0>{number = 11, name = (null)}
2018-02-27 18:29:42.915150+0800 GCD 系列知识点[1322:310167] 下载完成
2018-02-27 18:29:42.915664+0800 GCD 系列知识点[1322:310169] 下载图片:image10 线程:<NSThread: 0x604000471980>{number = 12, name = (null)}
2018-02-27 18:29:43.919164+0800 GCD 系列知识点[1322:310169] 下载完成

会发现调用的是并发队列,但是限制信号量总数为1 后,并发队列就变成了串行队列。

5.6.2 对多线程并发执行任务共享资源进行加锁处理

案例:总共有20张火车票,有两个售卖火车票的窗口,一个是北京火车票售卖窗口,另一个是上海火车票售卖窗口。两个窗口同时售卖火车票,卖完为止。
这里的问题核心是,两个窗口同时买票,在卖出一张票后总票数都会减1,如何确保票不多卖或卖叉?这就相当于计算机中的多条线程同时写一个资源,可能会出现混乱的问题。看Demo

-(void)testSaleTicketSemaphoreLock{
    
    self.allTicket = 20;
    
    //开辟两个线程来买票
    NSThread *oneThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    oneThread.name = @"北京";
    
    NSThread *twoThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    twoThread.name = @"上海";
    
    [oneThread start];
    [twoThread start];
}

-(void)saleTicket{
    
    while (1) {
        if (self.allTicket > 0){
            self.allTicket --;
            NSLog(@"剩余票数:%ld 当前窗口线程:%@",(long)self.allTicket,[NSThread currentThread].name);
            [NSThread sleepForTimeInterval:1];
        }else{
            NSLog(@"所有票都买完了");
            break;
        }
    }
}
2018-02-28 11:05:45.911240+0800 GCD 系列知识点[11745:1027862] 打印当前线程---<NSThread: 0x600000068140>{number = 1, name = main}
2018-02-28 11:05:45.911688+0800 GCD 系列知识点[11745:1028003] 剩余票数:19 当前窗口线程:北京
2018-02-28 11:05:45.911697+0800 GCD 系列知识点[11745:1028004] 剩余票数:18 当前窗口线程:上海
2018-02-28 11:05:46.916487+0800 GCD 系列知识点[11745:1028004] 剩余票数:17 当前窗口线程:上海
2018-02-28 11:05:46.916492+0800 GCD 系列知识点[11745:1028003] 剩余票数:16 当前窗口线程:北京
2018-02-28 11:05:47.922052+0800 GCD 系列知识点[11745:1028003] 剩余票数:15 当前窗口线程:北京
2018-02-28 11:05:47.922066+0800 GCD 系列知识点[11745:1028004] 剩余票数:14 当前窗口线程:上海
2018-02-28 11:05:48.925762+0800 GCD 系列知识点[11745:1028003] 剩余票数:13 当前窗口线程:北京
2018-02-28 11:05:48.925762+0800 GCD 系列知识点[11745:1028004] 剩余票数:12 当前窗口线程:上海
2018-02-28 11:05:49.930191+0800 GCD 系列知识点[11745:1028004] 剩余票数:11 当前窗口线程:上海
2018-02-28 11:05:49.930229+0800 GCD 系列知识点[11745:1028003] 剩余票数:11 当前窗口线程:北京
2018-02-28 11:05:50.934367+0800 GCD 系列知识点[11745:1028003] 剩余票数:10 当前窗口线程:北京
2018-02-28 11:05:50.934473+0800 GCD 系列知识点[11745:1028004] 剩余票数:9 当前窗口线程:上海
2018-02-28 11:05:51.937956+0800 GCD 系列知识点[11745:1028003] 剩余票数:8 当前窗口线程:北京
2018-02-28 11:05:51.937956+0800 GCD 系列知识点[11745:1028004] 剩余票数:8 当前窗口线程:上海
2018-02-28 11:05:52.942655+0800 GCD 系列知识点[11745:1028004] 剩余票数:7 当前窗口线程:上海
2018-02-28 11:05:52.942655+0800 GCD 系列知识点[11745:1028003] 剩余票数:7 当前窗口线程:北京
2018-02-28 11:05:53.945901+0800 GCD 系列知识点[11745:1028004] 剩余票数:6 当前窗口线程:上海
2018-02-28 11:05:53.945901+0800 GCD 系列知识点[11745:1028003] 剩余票数:6 当前窗口线程:北京
2018-02-28 11:05:54.949424+0800 GCD 系列知识点[11745:1028003] 剩余票数:5 当前窗口线程:北京
2018-02-28 11:05:54.949435+0800 GCD 系列知识点[11745:1028004] 剩余票数:4 当前窗口线程:上海
2018-02-28 11:05:55.952903+0800 GCD 系列知识点[11745:1028003] 剩余票数:3 当前窗口线程:北京
2018-02-28 11:05:55.952904+0800 GCD 系列知识点[11745:1028004] 剩余票数:2 当前窗口线程:上海
2018-02-28 11:05:56.956317+0800 GCD 系列知识点[11745:1028004] 剩余票数:1 当前窗口线程:上海
2018-02-28 11:05:56.956321+0800 GCD 系列知识点[11745:1028003] 剩余票数:0 当前窗口线程:北京
2018-02-28 11:05:57.961572+0800 GCD 系列知识点[11745:1028003] 所有票都买完了
2018-02-28 11:05:57.961599+0800 GCD 系列知识点[11745:1028004] 所有票都买完了

观察会发现余票在 11、8 、7、6的时候揣想那混乱,多卖了票。这就是多线程访问公共资源出现不同步的问题。我们使用dispatch_semaphore 来加锁处理一下,看看结果怎么样?

-(void)testSaleTicketSemaphoreLock{
    
    self.allTicket = 20;
    self.ticketSemaphore = dispatch_semaphore_create(1);
    
    //开辟两个线程来买票
    NSThread *oneThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    oneThread.name = @"北京";
    
    NSThread *twoThread = [[NSThread alloc]initWithTarget:self selector:@selector(saleTicket) object:nil];
    twoThread.name = @"上海";
    
    [oneThread start];
    [twoThread start];
}

-(void)saleTicket{
    
    while (1) {
        //加锁 如果信号量>0 执行wait 信号量减1 下方代码执行,如果信号量<0 那就不执行下方代码 阻塞当前
        dispatch_semaphore_wait(self.ticketSemaphore, DISPATCH_TIME_FOREVER);
        
        if (self.allTicket > 0){
            
            self.allTicket --;
            NSLog(@"剩余票数:%ld 当前窗口线程:%@",(long)self.allTicket,[NSThread currentThread].name);
            [NSThread sleepForTimeInterval:1];
        }else{
            NSLog(@"所有票都买完了");
            dispatch_semaphore_signal(self.ticketSemaphore);
            break;
        }
        //解锁 执行signal 信号量+1
        dispatch_semaphore_signal(self.ticketSemaphore);
    }
}
2018-02-28 14:17:28.118236+0800 GCD 系列知识点[13342:1195687] 打印当前线程---<NSThread: 0x604000068fc0>{number = 1, name = main}
2018-02-28 14:17:28.120479+0800 GCD 系列知识点[13342:1195906] 剩余票数:19 当前窗口线程:北京
2018-02-28 14:17:29.124787+0800 GCD 系列知识点[13342:1195907] 剩余票数:18 当前窗口线程:上海
2018-02-28 14:17:30.127421+0800 GCD 系列知识点[13342:1195906] 剩余票数:17 当前窗口线程:北京
2018-02-28 14:17:31.130508+0800 GCD 系列知识点[13342:1195907] 剩余票数:16 当前窗口线程:上海
2018-02-28 14:17:32.133827+0800 GCD 系列知识点[13342:1195906] 剩余票数:15 当前窗口线程:北京
2018-02-28 14:17:33.138644+0800 GCD 系列知识点[13342:1195907] 剩余票数:14 当前窗口线程:上海
2018-02-28 14:17:34.142570+0800 GCD 系列知识点[13342:1195906] 剩余票数:13 当前窗口线程:北京
2018-02-28 14:17:35.143104+0800 GCD 系列知识点[13342:1195907] 剩余票数:12 当前窗口线程:上海
2018-02-28 14:17:36.144129+0800 GCD 系列知识点[13342:1195906] 剩余票数:11 当前窗口线程:北京
2018-02-28 14:17:37.149313+0800 GCD 系列知识点[13342:1195907] 剩余票数:10 当前窗口线程:上海
2018-02-28 14:17:38.154850+0800 GCD 系列知识点[13342:1195906] 剩余票数:9 当前窗口线程:北京
2018-02-28 14:17:39.160380+0800 GCD 系列知识点[13342:1195907] 剩余票数:8 当前窗口线程:上海
2018-02-28 14:17:40.165849+0800 GCD 系列知识点[13342:1195906] 剩余票数:7 当前窗口线程:北京
2018-02-28 14:17:41.167534+0800 GCD 系列知识点[13342:1195907] 剩余票数:6 当前窗口线程:上海
2018-02-28 14:17:42.173125+0800 GCD 系列知识点[13342:1195906] 剩余票数:5 当前窗口线程:北京
2018-02-28 14:17:43.177330+0800 GCD 系列知识点[13342:1195907] 剩余票数:4 当前窗口线程:上海
2018-02-28 14:17:44.181994+0800 GCD 系列知识点[13342:1195906] 剩余票数:3 当前窗口线程:北京
2018-02-28 14:17:45.183557+0800 GCD 系列知识点[13342:1195907] 剩余票数:2 当前窗口线程:上海
2018-02-28 14:17:46.189193+0800 GCD 系列知识点[13342:1195906] 剩余票数:1 当前窗口线程:北京
2018-02-28 14:17:47.194826+0800 GCD 系列知识点[13342:1195907] 剩余票数:0 当前窗口线程:上海
2018-02-28 14:17:48.195913+0800 GCD 系列知识点[13342:1195906] 所有票都买完了
2018-02-28 14:17:48.196297+0800 GCD 系列知识点[13342:1195907] 所有票都买完了

会发现总剩余票数没有发生错位。其实dispatch_semaphore这里就是控制了多线程处理任务 并发时机,也是5.1.1的一种特殊形式。

5.6.3 页面有多个网络请求都完成后才处理任务

上面说的使用dispatch_group_enterdispatch_group_leave可以实现多网络请求完成监听处理。我们使用dispatch_semaphore也可以完成这种操作,具体直接上下面那段伪代码

dispatch_group_t group = dispatch_group_create();

//网络请求1
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
      //创建一个为0信号量
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       [异步网络请求1:{
          //成功或失败给信号加1
          成功Block:dispatch_semaphore_signal(semaphore);
          失败Block:dispatch_semaphore_signal(semaphore);
       }];
       //只有信号量>1 下面代码才走,方法才算执行完毕
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });

//网络请求2
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
       //创建一个为0信号量
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       [异步网络请求2:{
          //成功或失败给信号加1
          成功Block:dispatch_semaphore_signal(semaphore);
          失败Block:dispatch_semaphore_signal(semaphore);
       }];
       //只有信号量>1 下面代码才走,方法才算执行完毕
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });

//网络请求3
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        //创建一个为0信号量
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       [异步网络请求3:{
          //成功或失败给信号加1
          成功Block:dispatch_semaphore_signal(semaphore);
          失败Block:dispatch_semaphore_signal(semaphore);
       }];
       //只有信号量>1 下面代码才走,方法才算执行完毕
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    });

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
        NSLog(@"所有网络请求完毕");
    })

这样的组合就能实现多网络请求都有响应后的网络回调了!

补充: 那么如何让这些网络请求有个先后顺序了,就是说请求1返回回来的参数我要用到请求2上,这样的需求也很常见,用到的知识就不是GCD的了,而是NSOperation上的知识了,从别人博客弄来的代码,直接放上:

  //按照顺序
    NSBlockOperation *operation_1 = [NSBlockOperation blockOperationWithBlock:^{
        [self request1];
    }];
    NSBlockOperation *operation_2 = [NSBlockOperation blockOperationWithBlock:^{
        [self request2];
    }];
    NSBlockOperation *operation_3 = [NSBlockOperation blockOperationWithBlock:^{
        [self request3];
    }];
    //设置依赖
    [operation_2 addDependency:operation_1];
    [operation_3 addDependency:operation_1];
    //创建队列并添加任务
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    [queue addOperations:@[operation_3,operation_2,operation_1] waitUntilFinished:YES];

其中的[self request]里面代码还是使用信号量来控制网络请求真正结束,拿request1代码举个栗子:

-(void)request1{
       //创建一个为0信号量
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
       [异步网络请求3:{
          //成功或失败给信号加1
          成功Block:dispatch_semaphore_signal(semaphore);
          失败Block:dispatch_semaphore_signal(semaphore);
       }];
       //只有信号量>1 下面代码才走,方法才算执行完毕
       dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}

以上都是我根据别人的博客和自己代码实践所得,如有冒犯和不足欢迎大家批评。
感觉在微小简单的东西,如果系统学习起来,还是有盲点的,所以说所有的事情态度要端正,虚心点吧

引用博客
https://www.jianshu.com/p/2d57c72016c6
http://blog.csdn.net/Cloudox_/article/details/71107179
https://www.jianshu.com/p/228403206664
https://www.cnblogs.com/zhou--fei/p/6747938.html
https://www.jianshu.com/p/aa3cfcabb470

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

推荐阅读更多精彩内容

  • 本文首发于我的个人博客:「程序员充电站」[https://itcharge.cn]文章链接:「传送门」[https...
    ITCharge阅读 344,822评论 308 1,921
  • 背景 担心了两周的我终于轮到去医院做胃镜检查了!去的时候我都想好了最坏的可能(胃癌),之前在网上查的症状都很相似。...
    Dely阅读 9,178评论 21 42
  • 我的性格需要书写,因为欢乐生活中的我尤其沉默,秘密使我如有重负,步履艰难,大山中身背柴禾的人常常如此。我被秘密压迫...
    杨知行阅读 592评论 0 0
  • 1. 当一个人总用一种思维方式去解决问题时,他会很容易陷入困境。 其实事实的起源是一道数学题,今天晚上,我姨妈发微...
    劳伦斯公园阅读 882评论 11 14
  • (叁)回乡 第二天起来后,我继续去咖啡馆打工。小年夜过后,路上已经没有什么人,也不堵车了,看来,北京还是一个以外来...
    顧一念阅读 383评论 9 4