文章目录
  1. 1. 一. GCD的介绍
  2. 2. 二. 常用的GCD
    1. 2.1. 1. dispatch_semaphore_t,dispatch_semaphore_wait,dispatch_semaphore_signal
      1. 2.1.0.1. 作用:
      2. 2.1.0.2. 使用场景一:并发队列
      3. 2.1.0.3. 使用场景二:异步队列中做事,等待回调后执行某件事
      4. 2.1.0.4. 使用场景三:生产者,消费者
  3. 2.2. 2. dispatch_group_t,dispatch_group_notify
    1. 2.2.0.1. 使用场景一:并发队列
    2. 2.2.0.2. 使用场景二:多任务执行完成,刷新UI
  • 2.3. 3. dispatch_apply
  • 2.4. 4. dispatch_source_set_timer,dispatch_suspend,dispatch_resume,dispatch_source_cancel
  • 2.5. 5. dispatch_barrier_async
  • 2.6. 6. dispatch_sync和dispatch_async
    1. 2.6.0.1. 错误场景1: (死锁)结果无法输出(调用场景限于主线程调用主线程)
    2. 2.6.0.2. 错误场景2: (死锁)
  • 2.7. 7. dispatch_get_global_queue
  • 2.8. 8. dispatch_once
  • 2.9. 9. dispatch_after
  • 2.10. 10. dispatch_queue_set_specific和dispatch_queue_get_specific
  • 3. 三. 总结
  • 4. 四. Demo下载
  • 5. 五. 参考链接
  • 一. GCD的介绍

    GCD属于系统级的线程管理,在Dispatch queue中执行需要执行的任务性能非常的高。GCD这块已经开源,地址:http://libdispatch.macosforge.org。GCD中的FIFO队列称为dispatch queue,用来保证先进来的任务先得到执行。这里我们从作用和场景同时分析,看一下到底在哪些地方会使用。单单只介绍作用的话,可能

    二. 常用的GCD

    1. dispatch_semaphore_t,dispatch_semaphore_wait,dispatch_semaphore_signal

    作用:

    在多线程下控制多线程的并发数目。

    1. 创建信号量,可以设置信号量的资源数。0表示没有资源,调用dispatch_semaphore_wait会立即等待。
      dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
    2. 等待信号,可以设置超时参数。该函数返回0表示得到通知,非0表示超时。dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    3. 通知信号,如果等待线程被唤醒则返回非0,否则返回0。
      dispatch_semaphore_signal(semaphore);
    使用场景一:并发队列

    比如现在我每次想执行10个任务。休息两秒。继续执行10个任务。可以这么写.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    -(void) testSemaphore{
    dispatch_group_t group = dispatch_group_create();
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(10); //信号总量是10
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    for (int i = 0; i < 100; i++)
    {
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//信号量-1
    dispatch_group_async(group, queue, ^{
    NSLog(@"%i",i);
    sleep(2);
    dispatch_semaphore_signal(semaphore); //信号量+1
    });
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    }
    ```
    执行结果:

    ```objc
    2016-06-01 20:30:35.113 XXGCD[39559:306278] 7
    2016-06-01 20:30:35.113 XXGCD[39559:306277] 6
    2016-06-01 20:30:35.113 XXGCD[39559:306271] 0
    2016-06-01 20:30:35.113 XXGCD[39559:306274] 3
    2016-06-01 20:30:35.113 XXGCD[39559:306270] 1
    2016-06-01 20:30:35.113 XXGCD[39559:306272] 2
    2016-06-01 20:30:35.113 XXGCD[39559:306275] 4
    2016-06-01 20:30:35.113 XXGCD[39559:306276] 5
    2016-06-01 20:30:35.113 XXGCD[39559:306279] 8
    2016-06-01 20:30:35.113 XXGCD[39559:306280] 9

    2016-06-01 20:30:37.117 XXGCD[39559:306272] 11
    2016-06-01 20:30:37.117 XXGCD[39559:306270] 12
    2016-06-01 20:30:37.117 XXGCD[39559:306275] 10
    2016-06-01 20:30:37.117 XXGCD[39559:306280] 13
    2016-06-01 20:30:37.117 XXGCD[39559:306276] 15
    2016-06-01 20:30:37.117 XXGCD[39559:306279] 14
    2016-06-01 20:30:37.117 XXGCD[39559:306274] 16
    2016-06-01 20:30:37.117 XXGCD[39559:306277] 17
    2016-06-01 20:30:37.117 XXGCD[39559:306278] 18
    2016-06-01 20:30:37.117 XXGCD[39559:306271] 19
    ...
    使用场景二:异步队列中做事,等待回调后执行某件事
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    -(void) testAsynFinished{
    __block BOOL isok = NO;

    dispatch_semaphore_t sema = dispatch_semaphore_create(0);

    XXEngine *engine = [[XXEngine alloc] init];
    [engine queryCompletion:^(BOOL isOpen) {//sleep了3秒
    isok = isOpen;
    dispatch_semaphore_signal(sema);
    NSLog(@"success!");
    } onError:^(BOOL isOpen, int errorCode) {
    isok = NO;
    dispatch_semaphore_signal(sema);
    NSLog(@"error!");

    }];
    NSLog(@"finished");
    dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }

    执行结果:

    1
    2
    2016-06-02 15:01:06.168 XXGCD[35443:290673] success!
    2016-06-02 15:01:06.168 XXGCD[35443:290673] finished
    使用场景三:生产者,消费者
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    -(void) testProductAndConsumer{

    dispatch_semaphore_t sem = dispatch_semaphore_create(0);

    dispatch_queue_t producerQueue = dispatch_queue_create("producer", DISPATCH_QUEUE_CONCURRENT);//生产者线程跑的队列
    dispatch_queue_t consumerQueue = dispatch_queue_create("consumer", DISPATCH_QUEUE_CONCURRENT);//消费者线程跑的队列

    __block int cakeNumber = 0;
    dispatch_async(producerQueue, ^{ //生产者队列
    while (1) {
    if (!dispatch_semaphore_signal(sem))
    {
    NSLog(@"Product:生产出了第%d个蛋糕",++cakeNumber);
    sleep(1); //wait for a while
    continue;
    }
    }
    });
    dispatch_async(consumerQueue, ^{//消费者队列
    while (1) {
    if (dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 0*NSEC_PER_SEC))){
    if(cakeNumber > 0){
    NSLog(@"Consumer:拿到了第%d个蛋糕",cakeNumber--);
    }
    continue;
    }
    }
    });
    }

    执行结果:

    1
    2
    3
    4
    2016-06-02 15:04:18.785 XXGCD[35593:294211] Product:生产出了第1个蛋糕
    2016-06-02 15:04:18.785 XXGCD[35593:294209] Consumer:拿到了第1个蛋糕
    2016-06-02 15:04:19.790 XXGCD[35593:294209] Consumer:拿到了第1个蛋糕
    2016-06-02 15:04:19.790 XXGCD[35593:294211] Product:生产出了第1个蛋糕

    2. dispatch_group_t,dispatch_group_notify

    用来阻塞一个线程,直到一个或多个任务完成执行。有时候你必须等待任务完成的结果,然后才能继续后面的处理

    使用场景一:并发队列
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    -(void) testGCDGroup{
    dispatch_group_t group = dispatch_group_create();
    for(int i = 0; i < 10; i++)
    {
    dispatch_group_enter(group);
    [[XXEngine instance] doAsyncWorkWithCompletionBlock:^{
    dispatch_group_leave(group);
    }];
    }
    dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
    NSLog(@"testGCDGroup1 更新UI操作");
    }

    等价于:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    -(void) testGCDGroup2{
    dispatch_group_t group = dispatch_group_create();
    for(int i = 0; i < 10; i++)
    {
    dispatch_group_enter(group);
    [[XXEngine instance] doAsyncWorkWithCompletionBlock:^{
    dispatch_group_leave(group);
    }];
    }
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"testGCDGroup2 更新UI操作");
    });
    }
    ```

    执行结果:

    ```objc
    2016-06-02 15:29:54.392 XXGCD[36197:313551] test
    2016-06-02 15:29:54.392 XXGCD[36197:313569] test
    2016-06-02 15:29:54.393 XXGCD[36197:313572] test
    2016-06-02 15:29:54.393 XXGCD[36197:313571] test
    2016-06-02 15:29:54.392 XXGCD[36197:313558] test
    2016-06-02 15:29:54.392 XXGCD[36197:313568] test
    2016-06-02 15:29:54.392 XXGCD[36197:313563] test
    2016-06-02 15:29:54.393 XXGCD[36197:313570] test
    2016-06-02 15:29:54.393 XXGCD[36197:313573] test
    2016-06-02 15:29:54.393 XXGCD[36197:313574] test
    2016-06-02 15:29:56.398 XXGCD[36197:313513] testGCDGroup1 更新UI操作
    使用场景二:多任务执行完成,刷新UI

    需要在主线程中执行,比如操作GUI,那么我们只要将main queue而非全局队列传给dispatch_group_notify函数就行了。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    -(void) testGCDGroup3{
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_t group = dispatch_group_create();
    dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:1];
    NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:2];
    NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
    [NSThread sleepForTimeInterval:3];
    NSLog(@"group3");
    });
    dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"testGCDGroup3 更新UI操作");
    });
    }

    执行结果:

    1
    2
    3
    4
    2016-06-02 15:58:29.041 XXGCD[36675:337871] group1
    2016-06-02 15:58:30.041 XXGCD[36675:337880] group2
    2016-06-02 15:58:31.043 XXGCD[36675:337884] group3
    2016-06-02 15:58:31.043 XXGCD[36675:337834] testGCDGroup3 更新UI操作

    3. dispatch_apply

    是同步函数,会阻塞当前线程直到所有循环迭代执行完成。当提交到并发queue时,循环迭代的执行顺序是不确定的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static int i = 0;
    -(void) testGCDApply{
    dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_apply(10, globalQ, ^(size_t index) {
    // 执行10次
    [self printTest];
    });
    }
    -(void) printTest{
    NSLog(@"%d",++i);
    }

    执行结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    2016-06-02 16:06:50.799 XXGCD[37133:347348] 6
    2016-06-02 16:06:50.799 XXGCD[37133:347329] 2
    2016-06-02 16:06:50.799 XXGCD[37133:347346] 8
    2016-06-02 16:06:50.799 XXGCD[37133:347336] 1
    2016-06-02 16:06:50.799 XXGCD[37133:347340] 3
    2016-06-02 16:06:50.799 XXGCD[37133:347296] 4
    2016-06-02 16:06:50.799 XXGCD[37133:347347] 7
    2016-06-02 16:06:50.799 XXGCD[37133:347349] 5
    2016-06-02 16:06:50.799 XXGCD[37133:347329] 9
    2016-06-02 16:06:50.799 XXGCD[37133:347348] 10

    4. dispatch_source_set_timer,dispatch_suspend,dispatch_resume,dispatch_source_cancel

    1
    2
    3
    `dispatch_suspend`:暂停`dispatch_source_t` 
    `dispatch_resume`:恢复`dispatch_source_t`
    `dispatch_cancel`:取消`dispatch_source_t`

    众所周知,定时器有NSTimer,但是NSTimer有如下弊端:

    1. 必须保证有一个活跃的runloop,子线程的runloop是默认关闭的。这时如果不手动激活runloop,performSelector和scheduledTimerWithTimeInterval的调用将是无效的
    2. NSTimer的创建与撤销必须在同一个线程操作、performSelector的创建与撤销必须在同一个线程操作。
    3. 内存管理有潜在泄露的风险
    1
    _timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(doSometing) userInfo:nil repeats:YES];

    如果此时不调用

    1
    [_timer invalidate];

    是无法停止的。还有持有self,造成对象无法释放。
    所以在此我还是比较推荐用dispatch的timer,无须考虑这些事情

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    @interface XXGCDTimer()
    {
    dispatch_source_t _timer;
    }
    @end

    @implementation XXGCDTimer
    -(void) startGCDTimer{
    NSTimeInterval period = 1.0; //设置时间间隔
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), period * NSEC_PER_SEC, 0); //每秒执行
    dispatch_source_set_event_handler(_timer, ^{
    //在这里执行事件
    NSLog(@"每秒执行test");
    });

    dispatch_resume(_timer);
    }

    -(void) pauseTimer{
    if(_timer){
    dispatch_suspend(_timer);
    }
    }

    -(void) resumeTimer{
    if(_timer){
    dispatch_resume(_timer);
    }
    }

    -(void) stopTimer{
    if(_timer){
    dispatch_source_cancel(_timer);
    _timer = nil;
    }
    }
    @end

    5. dispatch_barrier_async

    这里之前的文章有非常详细的解释以及demo在文章后面。可以参考 iOS异步读写总结之NSDictionary,这里就不去详细说明了。 注意一下DISPATCH_QUEUE_CONCURRENT(并行队列)和DISPATCH_QUEUE_SERIAL(串行队列)即可

    6. dispatch_syncdispatch_async

    这里一个是同步,一个是异步。只要注意一下死锁的问题就好了。

    错误场景1: (死锁)结果无法输出(调用场景限于主线程调用主线程)
    1
    2
    3
    4
    5
    -(void) startSYNC{
    dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"3");
    });
    }

    原因:

    1:dispatch_sync在等待block语句执行完成,而block语句需要在主线程里执行,所以dispatch_sync如果在主线程调用就会造成死锁
    2:dispatch_sync是同步的,本身就会阻塞当前线程,也即主线程。而又往主线程里塞进去一个block,所以就会发生死锁。

    错误场景2: (死锁)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    - (void)testSYNC2 {
    dispatch_queue_t aSerialDispatchQueue =
    dispatch_queue_create("I.am.an.iOS.developer", DISPATCH_QUEUE_SERIAL);

    dispatch_sync(aSerialDispatchQueue, ^{
    // block 1
    NSLog(@"Good Night, Benjamin.");
    dispatch_sync(aSerialDispatchQueue, ^{
    // block 2
    NSLog(@"Good Night, Daisy.");
    });
    });
    }
    ```
    原因:
    简单来说,在dispatch_sync嵌套使用时要注意:不能在一个嵌套中使用同一个serial dispatch queue,因为会发生死锁;

    ##### 正确用法:

    ```objc
    dispatch_async(dispatch_get_main_queue(), ^{
    //async 异步队列,dispatch_async 函数会立即返回, block会在后台异步执行。
    NSLog(@"2");//不会造成死锁;
    });

    当然还有一种更优秀的做法,请看最后面。

    7. dispatch_get_global_queue

    1
    2
    3
    4
    5
    6
    7
    8
    9
    -(void) test{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    // 耗时的操作
    dispatch_async(dispatch_get_main_queue(), ^{
    // 更新界面
    NSLog(@"update UI");
    });
    });
    }

    全局并发队列的优先级

    1
    2
    3
    4
    #define DISPATCH_QUEUE_PRIORITY_HIGH 2 // 高
    #define DISPATCH_QUEUE_PRIORITY_DEFAULT 0 // 默认(中)
    #define DISPATCH_QUEUE_PRIORITY_LOW (-2) // 低
    #define DISPATCH_QUEUE_PRIORITY_BACKGROUND INT16_MIN // 后台

    注意:
    在实际修改bug的时候发现,global全局队列并不会反复创建新的线程,在系统启动后底层会维护一个线程池的概念,一旦用Global的队列结束,会缓存一段时间。因为在测试的时候可以打印出Thread ID。在多线程情况下发现一些ThreadID是一样的,由此得出结论。

    8. dispatch_once

    1
    2
    3
    4
    5
    // 使用dispatch_once函数能保证某段代码在程序运行过程中只被执行1次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    // 只执行1次的代码(这里面默认是线程安全的)
    });

    9. dispatch_after

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

    10. dispatch_queue_set_specificdispatch_queue_get_specific

    iOS 6之后dispatch_get_current_queue()被废弃,如果我们需要区分不同的queue,可以使用set_specific方法。根据对应的key是否有值来区分。这里提供了同步获取和异步获取两种方式供参考

    #import "XXGCDSpecific.h"
    
    static const void * kAsyncDispatchQueueSpecificKey = &kAsyncDispatchQueueSpecificKey;
    static const void * kSyncDispatchQueueSpecificKey = &kSyncDispatchQueueSpecificKey;
    
    @interface XXGCDSpecific()
    
    @property(nonatomic,strong) dispatch_queue_t queueAsync;
    @property(nonatomic,strong) dispatch_queue_t queueSync;
    
    @end
    
    @implementation XXGCDSpecific
    
    -(instancetype)init{
        if (self = [super init]) {
            _queueAsync = dispatch_queue_create([[NSString stringWithFormat:@"fmdbASync.%@",self] UTF8String], NULL);
            dispatch_queue_set_specific(_queueAsync, kAsyncDispatchQueueSpecificKey, (__bridge void * _Nullable)(self), NULL);
    
            _queueSync = dispatch_queue_create([[NSString stringWithFormat:@"fmdbSync.%@",self] UTF8String], NULL);
            dispatch_queue_set_specific(_queueSync, kSyncDispatchQueueSpecificKey, (__bridge void * _Nullable)(self), NULL);
    
        }
        return self;
    }
    
    #pragma mark- Async
    -(void) startAsyncSpecific{
        [self performAsyncBlock:^{
            NSLog(@"EndtAsyncSpecific!!");
        }];
    }
    
    -(void) performAsyncBlock:(void(^)()) block
    {
        if(block == nil){
            return;
        }
        //如果当前线程是 kAsyncDispatchQueueSpecificKey 所在的线程
        if(dispatch_get_specific(kAsyncDispatchQueueSpecificKey)){
            block();
        }else{
            dispatch_async(_queueAsync, block);
        }
    }
    
    
    -(void) startSyncSpecific{
        int result = [self getResult];
    
        NSLog(@"result:%d",result);
    }
    
    -(int) getResult{
        __block int result = 5;
        dispatch_block_t block = ^{
            result = 1;
        };
    
        if(dispatch_get_specific(kSyncDispatchQueueSpecificKey)){
            block();
        }else{
            dispatch_sync(_queueSync, block);
        }
        return result;
    }
    @end
    

    三. 总结

    这里是对GCD做总结的同时,突出了一些使用场景。因为在开发过程中,我们不仅仅知道这东西是啥,更要知道什么时候灵活运用,这里提供了一些解决思路。关于Block相关的方法可以参考Block那些事.最后欢迎加入QQ群:237305299。一起探讨iOS技术问题。

    四. Demo下载

    Demo

    五. 参考链接

    1. 关于dispatch_semaphore的使用
    2. iOS多线程中的dispatch_semaphore_t semaphore(dispatch组和信号量。)
    3. Wait until multiple networking requests have all executed - including their completion blocks
    4. 选择 GCD 还是 NSTimer ?
    5. 深入理解dispatch_sync
    文章目录
    1. 1. 一. GCD的介绍
    2. 2. 二. 常用的GCD
      1. 2.1. 1. dispatch_semaphore_t,dispatch_semaphore_wait,dispatch_semaphore_signal
        1. 2.1.0.1. 作用:
        2. 2.1.0.2. 使用场景一:并发队列
        3. 2.1.0.3. 使用场景二:异步队列中做事,等待回调后执行某件事
        4. 2.1.0.4. 使用场景三:生产者,消费者
    3. 2.2. 2. dispatch_group_t,dispatch_group_notify
      1. 2.2.0.1. 使用场景一:并发队列
      2. 2.2.0.2. 使用场景二:多任务执行完成,刷新UI
  • 2.3. 3. dispatch_apply
  • 2.4. 4. dispatch_source_set_timer,dispatch_suspend,dispatch_resume,dispatch_source_cancel
  • 2.5. 5. dispatch_barrier_async
  • 2.6. 6. dispatch_sync和dispatch_async
    1. 2.6.0.1. 错误场景1: (死锁)结果无法输出(调用场景限于主线程调用主线程)
    2. 2.6.0.2. 错误场景2: (死锁)
  • 2.7. 7. dispatch_get_global_queue
  • 2.8. 8. dispatch_once
  • 2.9. 9. dispatch_after
  • 2.10. 10. dispatch_queue_set_specific和dispatch_queue_get_specific
  • 3. 三. 总结
  • 4. 四. Demo下载
  • 5. 五. 参考链接