GCD简析

Grand Central Dispatch是一种异步执行任务技术。Dispatch Queue是执行处理的等待队列。它按照追加的顺序(FIFO)执行处理。Dispatch Queue按照是否等待处理可以分为serial Dispatch Queue和Concurrent Dispatch Queue。serial Dispatch Queue中的block只在一个线程中顺序执行,而Concurrent Dispatch Queue中的block会在多个线程中并行执行。
image.png

GCD使用简介

dispatch_queue

dispatch_queue_t可以通过dispatch_queue_create生成自定义队列,也可以使用dispatch_get_global_queuedispatch_get_main_queue函数来生成全局队列和主队列。
dispatch_queue_create中的第一个参数label表示queue的名称,attr表示是否是并行队列。dispatch_queue_create创建的队列默认优先级为Default Priority

dispatch_queue_create(const char *_Nullable label,
    dispatch_queue_attr_t _Nullable attr);

如果想修改dispatch_queue_create创建的队列的优先级,可以使用dispatch_set_target_queue

dispatch_queue_t serialQueue = dispatch_queue_create("com.cmcc.gcdserial", NULL);
dispatch_queue_t backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
//设置serialQueue优先级为backgroundQueue的优先级
dispatch_set_target_queue(serialQueue, backgroundQueue);   

同时在多个serial dispatch Queue中,可以通过设置dispatch_set_target_queue使多个queue串行执行。

dispatch_get_global_queue用来创建一个全局队列。第一个参数identifier表示队列的优先级,第二个参数为保留字段。优先级有High PriorityDefault PriorityLow PriorityBackground Priority4种(在iOS8.0之前)和QOS_CLASS_USER_INTERACTIVEQOS_CLASS_USER_INITIATEDQOS_CLASS_DEFAULTQOS_CLASS_UTILITYQOS_CLASS_UTILITYQOS_CLASS_UNSPECIFIED5种(iOS8.0及之后)。
全局队列默认都是并发队列。

dispatch_get_global_queue(long identifier, unsigned long flags);

dispatch_get_main_queue用来获得主队列。主队列是一个串行队列。

dispatch_get_main_queue()

dispatch_async & dispatch_sync

dispatch_async表示异步执行队列,dispatch_sync表示同步执行队列。异步就是不等待处理结果,同步就是等待处理结果。不过对于串行队列,dispatch_asyncdispatch_sync效果一样。

__block NSString *serialStr = @"I am serial";
dispatch_queue_t serialQueue = dispatch_queue_create("com.cmcc.gcdserial", NULL);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_sync(serialQueue, ^{
        NSLog(@"no.1 %@",serialStr);
        serialStr = @"I am changed";
    });
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_sync(serialQueue, ^{
        NSLog(@"no.2 %@",serialStr);
    });
});
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    dispatch_sync(serialQueue, ^{
        NSLog(@"no.3 %@",serialStr);
    });
});

dispatch_sync处理不当容易引起死锁。因此使用时需慎重。比如在主线程中执行以下代码。

dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"I am locked");
});

dispatch_after

dispatch_after用于延迟一定时间把执行得block加入到队列中。同时这个延时不是精确的,它取决于队列线程runloop,可能会跳过一个runloop周期。

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3ull * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"wait at least 3 seconds");  //3s后追加这block到queue
});

dispatch_group

dispatch_group用于对多个处理全部结束后执行处理结果。group通过dispatch_group_create创建,queue通过dispatch_get_global_queue创建。可以通过dispatch_group_async向group中加入block,或者通过dispatch_group_enterdispatch_group_leave之间加入block。 dispatch_notify用于前面group中加入的block执行完后通知执行处理结果。dispatch_wait用于等待前面group中加入的block执行。dispatch_wait会阻塞线程,dispatch_notify不会阻塞线程。

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
__block NSMutableArray *arr = [NSMutableArray arrayWithCapacity:0];
dispatch_group_async(group, queue, ^{
    [arr addObject:@1];
});
dispatch_group_async(group, queue, ^{
    [arr addObject:@2];
});
dispatch_notify(group, queue, ^{
    dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"update UI with arr %@", arr);
    });
});

dispatch_barrier

对于数据库和文件的读写,往往希望并行读,同步写。这种情形可以通过dispatch_barrier实现。barrier就像一个栅栏,把queue分为barrier之前和barrier之后。先执行barrier之前的,再执行barrier之中的,最后执行barrier之后的。

__block NSString *barrierStr = @"barrier init";
dispatch_queue_t barrierqueue = dispatch_queue_create("com.cmcc.gcdbarrier", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(barrierqueue, ^{
    NSLog(@"read 1 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
    NSLog(@"read 2 %@",barrierStr);
});
dispatch_barrier_async(barrierqueue, ^{
    barrierStr = @"barrier changed";
    NSLog(@"write 3 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
    NSLog(@"read 4 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
    NSLog(@"read 5 %@",barrierStr);
});
dispatch_async(barrierqueue, ^{
    NSLog(@"read 6 %@",barrierStr);
});

dispatch_apply

dispatch_apply效果与dispatch_group效果类似。但是多了一个次数的参数。

dispatch_apply(6, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t index) {
    NSLog(@"%zu",index);
});
NSLog(@"done");

执行结果为:

2018-01-03 12:54:57.690983+0800 GCDDemo[19151:3684607] 2
2018-01-03 12:54:57.690987+0800 GCDDemo[19151:3684605] 1
2018-01-03 12:54:57.690990+0800 GCDDemo[19151:3684608] 3
2018-01-03 12:54:57.690993+0800 GCDDemo[19151:3684670] 5
2018-01-03 12:54:57.690988+0800 GCDDemo[19151:3684509] 4
2018-01-03 12:54:57.690983+0800 GCDDemo[19151:3684606] 0
2018-01-03 12:54:57.692172+0800 GCDDemo[19151:3684509] done

dispatch_block

dispatch_block_t是在iOS8及之后引入的,用于监听和取消执行的block。监听通过dispatch_waitdispatch_notifyblock的执行。另外通过dispatch_block_cancel来取消执行的block。这个只能取消还没执行的block。

dispatch_queue_t serialQueue = dispatch_queue_create("com.cmcc.serial", NULL);
dispatch_block_t block1 = dispatch_block_create(0, ^{
    NSLog(@"start block1");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"end block1");
});
dispatch_async(serialQueue, block1);
long result = dispatch_wait(block1, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
if (result == 0) {
    NSLog(@"success perform block1");
} else {
    NSLog(@"time out");
}

dispatch_block_t block2 = dispatch_block_create(0, ^{
    NSLog(@"start block2");
    [NSThread sleepForTimeInterval:3];
    NSLog(@"end block2");
});
dispatch_async(serialQueue, block2);
dispatch_block_cancel(block1);
dispatch_block_cancel(block2);

dispatch_semaphore_t

dispatch_semaphore_t表示一个信号量。如果信号量不大于0,dispatch_wait则会阻塞线程。如果用dispatch_semaphore_signal则会为该semaphore增加一个计数。可以用semaphore来做同步操作。

dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
NSMutableArray *array = [NSMutableArray new];
for (int i=0; i<10000; i++) {
    dispatch_async(globalQueue, ^{
        dispatch_wait(semaphore, DISPATCH_TIME_FOREVER);
        [array addObject:[NSNumber numberWithInt:i]];
        NSLog(@"current number is %d",i);
        dispatch_semaphore_signal(semaphore);
    });
}

dispatch_once

dispatch_once是用来创建单例的,它比传统的单例创建比优势是多线程安全的。

static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
    //init
});

dispatch_source

Dispatch Source是BSD内核功能kqueue的包装。kqueue是在XNU内核发生各种事件时,在应用程序编程执行处理的方式。
Dispatch Source可以处理以下事件:

名称 内容
DISPATCH_SOURCE_TYPE_DATA_ADD 变量增加
DISPATCH_SOURCE_TYPE_DATA_OR 变量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 检测到与进程相关的事件
DISPATCH_SOURCE_TYPE_READ 可读取文件映像
DISPATCH_SOURCE_TYPE_SIGNAL 接收信号
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件系统有变更
DISPATCH_SOURCE_TYPE_WRITE 可写入文件映像

下面是GCDAsyncSocket使用DISPATCH_SOURCE_TYPE_READ异步读取文件映像的例子。

//1.创建基于DISPATCH_SOURCE_TYPE_READ的dispatch source
accept4Source = dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, socket4FD, 0, socketQueue);

int socketFD = socket4FD;
dispatch_source_t acceptSource = accept4Source;

__weak IDMPGCDAsyncSocket *weakSelf = self;

//2.指定read事件发生时执行的处理        
dispatch_source_set_event_handler(accept4Source, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"

    __strong IDMPGCDAsyncSocket *strongSelf = weakSelf;
    if (strongSelf == nil) return_from_block;

    LogVerbose(@"event4Block");

    unsigned long i = 0;
    unsigned long numPendingConnections = dispatch_source_get_data(acceptSource);

    LogVerbose(@"numPendingConnections: %lu", numPendingConnections);

    while ([strongSelf doAccept:socketFD] && (++i < numPendingConnections));

#pragma clang diagnostic pop
}});

//3.指定取消source时执行的处理        
dispatch_source_set_cancel_handler(accept4Source, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"

    #if !OS_OBJECT_USE_OBJC
    LogVerbose(@"dispatch_release(accept4Source)");
    dispatch_release(acceptSource);
    #endif

    LogVerbose(@"close(socket4FD)");
    close(socketFD);

#pragma clang diagnostic pop
});

LogVerbose(@"dispatch_resume(accept4Source)");
//4.启动dispatch source
dispatch_resume(accept4Source);

下面这个是GCDAsyncSocket使用DISPATCH_SOURCE_TYPE_TIMER的例子。

//1.创建基于DISPATCH_SOURCE_TYPE_TIMER的dispatch source
connectTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, socketQueue);

__weak IDMPGCDAsyncSocket *weakSelf = self;
//2.设置超时时的处理        
dispatch_source_set_event_handler(connectTimer, ^{ @autoreleasepool {
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"

    __strong IDMPGCDAsyncSocket *strongSelf = weakSelf;
    if (strongSelf == nil) return_from_block;

    [strongSelf doConnectTimeout];

#pragma clang diagnostic pop
}});

#if !OS_OBJECT_USE_OBJC
dispatch_source_t theConnectTimer = connectTimer;
//3.设置source取消时的处理        
dispatch_source_set_cancel_handler(connectTimer, ^{
#pragma clang diagnostic push
#pragma clang diagnostic warning "-Wimplicit-retain-self"

    LogVerbose(@"dispatch_release(connectTimer)");
    dispatch_release(theConnectTimer);

#pragma clang diagnostic pop
});
#endif

dispatch_time_t tt = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeout * NSEC_PER_SEC));
//4.设置定时器超时时间
dispatch_source_set_timer(connectTimer, tt, DISPATCH_TIME_FOREVER, 0);
//5.启动dispatch source
dispatch_resume(connectTimer);

参考

Dispatch
Objective-C高级编程
细说GCD(Grand Central Dispatch)如何用
CocoaAsyncSocket
深入理解GCD

Author: MrHook
Link: https://bigjar.github.io/2018/01/29/GCD%E7%AE%80%E6%9E%90/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.