iOS GCD简介(二)

队列标识

dispatch_queue_set_specific给指定队列添加一个标识;
dispatch_get_specific从当前线程取出队列标识,判断是不是指定队列。

        _dataDispatchQueue = dispatch_queue_create("com.test.SocketRequest", NULL);
        IsDataDispatchQueueKey = &IsDataDispatchQueueKey;
        void *nonNullUnusedPointer = (__bridge void *)self;
        dispatch_queue_set_specific(_dataDispatchQueue, IsDataDispatchQueueKey, nonNullUnusedPointer, NULL);


- (uint32_t)operType {
    if (dispatch_get_specific(IsDataDispatchQueueKey)) {
        return _operType;
    } else {
        __block uint32_t result;
        
        dispatch_sync(self.dataDispatchQueue, ^{
            result = self.operType;
        });
        
        return result;
    }
}

队列组

用于多个任务完成后通知执行下步操作,常用于完成多个网络请求后刷新界面。

使用步骤:
1.创建dispatch_group_t对象;
2.创建任务执行的队列
3.调用dispatch_group_async方法添加执行任务;
4.任务执行完成后会调用dispatch_group_notify方法。

此处任务完成仅仅是将dispatch_group_async方法内任务代码执行完毕,如果此任务可能开启另一个异步进行数据处理,不会等待新开的异步处理。如添加一个AFN网络请求的任务,当网络请求建立完成后就认为任务结束,而不是等待网络返回请求数据。此时我们需要引入两个新的方法dispatch_group_enterdispatch_group_leave,在开启网络时调用dispatch_group_enter,等到返回网络请求数据后再调用dispatch_group_leave,这样系统调用dispatch_group_notify方法时才能正确的处理下一步操作。

注意:当项目中使用dispatch_group_enterdispatch_group_leave时,两个方法要成对调用。如果dispatch_group_enter执行次数大于dispatch_group_leave方法则系统不会调用dispatch_group_notify方法;如果dispatch_group_leave方法执行次数大于dispatch_group_enter程序就会崩溃。

示例:

@property (nonatomic, strong, nullable) dispatch_group_t completionGroup;
    __weak typeof(self)weakSelf = self;
    self.completionGroup = dispatch_group_create();
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dispatch_group_async(self.completionGroup, queue, ^{
        dispatch_group_enter(weakSelf.completionGroup);
        [[NetworkRequest sharedInstance] requestFirst:@{}
                                         successBlock:^(id  _Nullable responseObject) {
            
            dispatch_group_leave(weakSelf.completionGroup);
        } errorBlock:^(NSError *error) {
            
            dispatch_group_leave(weakSelf.completionGroup);
        } failureBlock:^(NSError *error) {
            
            dispatch_group_leave(weakSelf.completionGroup);
        }];
    });
    
    dispatch_group_async(self.completionGroup, queue, ^{
        dispatch_group_enter(weakSelf.completionGroup);
        [[NetworkRequest sharedInstance] requestSecondCompletionResult:^(id  _Nullable responseObject) {
            
            dispatch_group_leave(weakSelf.completionGroup);
        }];
    });
    
    
    //因上面请求中有加入dispatch_group_enter 和 dispatch_group_leave,所以真正等待上面线程执行完才执行这里面的请求
    dispatch_group_notify(self.completionGroup, dispatch_get_main_queue(), ^{
        
    });

定时器

相对于NSTimer,更加精准,不受RunLoop影响

static const CGFloat kTime = 3.f;
static const CGFloat kIntervalTime = 0.1f;

@property (strong,nonatomic)dispatch_source_t timer;

    __weak typeof(self)weakSelf = self;
    __block CGFloat time = kTime; //倒计时时间
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
    dispatch_source_set_timer(self.timer,dispatch_walltime(NULL, 0),kIntervalTime*NSEC_PER_SEC, 0); //每0.1秒执行
    dispatch_source_set_event_handler(self.timer, ^{
        if(time <= 0){ //倒计时结束,关闭
            if (weakSelf.timer) {
                dispatch_source_cancel(weakSelf.timer);
            }
            dispatch_async(dispatch_get_main_queue(), ^{
               
            });
            
        }else{
            time = time - kIntervalTime;
        }
    });
    dispatch_resume(self.timer);

注意:dispatch_suspend(self.timer)暂停timer事件后,不能之间调用调用self.timer = nil释放timer,否则引起崩溃。
详见:GCD定时器(dispatch_source_t)使用以及防止crash

dispatch_suspend/dispatchp_resume

dispatch_suspend挂起队列,dispatchp_resume恢复队列。
队列挂起后已开启任务无法停止,未开启任务将等到resume后开始执行。

栅栏函数

dispatch barrier API是一种向调度队列提交屏障块的机制。它可以实现高效的读写器方案。

屏障块只有在提交到使用DISPATCH_QUEUE_CONCURRENT属性创建的队列时才会表现出特殊的行为;在这样的队列中,在之前提交到队列的所有块都完成之前,屏障块才会运行,并且在屏障块完成之前提交到队列的任何块都不会运行。

当提交到全局队列或不是使用DISPATCH_QUEUE_CONCURRENT属性创建的队列时,屏障块的行为与使用dispatch_async()/dispatch_sync()API提交的块的行为相同。

dispatch_barrier_async

dispatch_barrier_async: Submits a barrier block for asynchronous execution and returns immediately.(提交一个栅栏函数在异步执行中,它会立马返回)

    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"%@--开始",[NSThread currentThread]);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--1",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--2",[NSThread currentThread]);
    });
    dispatch_barrier_async(concurrentQueue, ^{
        NSLog(@"%@--dispatch_barrier_async",[NSThread currentThread]);
    });
    NSLog(@"%@--栅栏",[NSThread currentThread]);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--3",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--4",[NSThread currentThread]);
    });
    NSLog(@"%@--结束",[NSThread currentThread]);

结果:

{number = 1, name = main}--开始
{number = 1, name = main}--栅栏
{number = 6, name = (null)}--1
{number = 7, name = (null)}--2
{number = 1, name = main}--结束
{number = 6, name = (null)}--dispatch_barrier_async
{number = 6, name = (null)}--3
{number = 7, name = (null)}--4
dispatch_barrier_sync

dispatch_barrier_sync: Submits a barrier block object for execution and waits until that block completes.(提交一个栅栏函数在执行中,它会等待栅栏函数执行完)

    dispatch_queue_t concurrentQueue = dispatch_queue_create("com.test.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    NSLog(@"%@--开始",[NSThread currentThread]);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--1",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--2",[NSThread currentThread]);
    });
    dispatch_barrier_sync(concurrentQueue, ^{
        NSLog(@"%@--dispatch_barrier_sync",[NSThread currentThread]);
    });
    NSLog(@"%@--栅栏",[NSThread currentThread]);
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--3",[NSThread currentThread]);
    });
    dispatch_async(concurrentQueue, ^{
        NSLog(@"%@--4",[NSThread currentThread]);
    });
    NSLog(@"%@--结束",[NSThread currentThread]);

结果:

{number = 1, name = main}--开始
{number = 10, name = (null)}--1
{number = 9, name = (null)}--2
{number = 1, name = main}--dispatch_barrier_sync
{number = 1, name = main}--栅栏
{number = 1, name = main}--结束
{number = 9, name = (null)}--3
{number = 10, name = (null)}--4
异同点

共同点:
1、等待在它前面插入队列的任务先执行完
2、等待他们自己的任务执行完再执行后面的任务

不同点:
1、dispatch_barrier_sync将自己的任务插入到队列的时候,需要等待自己的任务结束之后才会继续插入被写在它后面的任务,然后执行它们
2、dispatch_barrier_async将自己的任务插入到队列之后,不会等待自己的任务结束,它会继续把后面的任务插入到队列,然后等待自己的任务结束后才执行后面任务。

信号量:dispatch_semaphore

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。信号量常用于对资源进行加锁操作,防止多线程访问修改数据出现结果不一致导致异常问题。

/**
使用初始值创建新的计数信号量。
当两个线程需要协调特定事件的完成时,为该值传递零非常有用。传递大于零的值对于管理有限的资源池非常有用,其中池大小等于该值。

入参:信号量的起始值。传递小于零的值将导致返回NULL。
结果:新创建的信号量,或失败时为NULL。

**/
dispatch_semaphore_t dispatch_semaphore_create(long value);

/**
等待(减量)信号量。
减少计数信号量。如果结果值小于零,此函数将等待信号出现,然后返回。
第一个参数:信号量对象。此参数中传递NULL的结果未定义。
第二个参数:何时超时(参见调度时间)。为了方便起见,这里有DISPATCH_TIME_NOW和DISPATCH_TIME_FOREVER常量。

结果:成功时返回零;如果超时,则返回非零。
**/
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout);

/**
发信号(递增)信号量。
递增计数信号量。如果先前的值小于零,此函数将在返回之前唤醒等待的线程。

入参:计数信号量。此参数中传递NULL的结果未定义。
结果:如果线程被唤醒,此函数返回非零。否则,返回零。
**/
long dispatch_semaphore_signal(dispatch_semaphore_t dsema);
原理

本小节摘抄至GCD之dispatch_semaphore

dispatch_semaphore_t

首先看一下dispatch_semaphore_s的结构体定义:

struct dispatch_semaphore_s {
    DISPATCH_STRUCT_HEADER(semaphore);
    semaphore_t dsema_port;    //等同于mach_port_t信号
    long dsema_orig;           //初始化的信号量值
    long volatile dsema_value; //当前信号量值
    union {
        long volatile dsema_sent_ksignals;
        long volatile dsema_group_waiters;
    };
    struct dispatch_continuation_s *volatile dsema_notify_head; //notify的链表头部
    struct dispatch_continuation_s *volatile dsema_notify_tail; //notify的链表尾部
};

dispatch_semaphore_create

dispatch_semaphore_create用来创建信号量,创建时需要指定value,内部会将value的值存储到dsema_value(当前的value)和dsema_orig(初始value)中,value的值必须大于或等于0。

dispatch_semaphore_t dispatch_semaphore_create(long value) {
    dispatch_semaphore_t dsema;
    if (value < 0) {
       //value值需大于或等于0
        return NULL;
    }
  //申请dispatch_semaphore_t的内存
    dsema = (dispatch_semaphore_t)_dispatch_alloc(DISPATCH_VTABLE(semaphore),
            sizeof(struct dispatch_semaphore_s) -
            sizeof(dsema->dsema_notify_head) -
            sizeof(dsema->dsema_notify_tail));
    //调用初始化函数
    _dispatch_semaphore_init(value, dsema);
    return dsema;
}
//初始化结构体信息
static void _dispatch_semaphore_init(long value, dispatch_object_t dou) {
    dispatch_semaphore_t dsema = dou._dsema;
    dsema->do_next = (dispatch_semaphore_t)DISPATCH_OBJECT_LISTLESS;
    dsema->do_targetq = dispatch_get_global_queue(
            DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    dsema->dsema_value = value; //设置信号量的当前value值
    dsema->dsema_orig = value;  //设置信号量的初始value值
}

接着来看Dispatch Semaphore很容易忽略也是最容易造成App崩溃的地方,即信号量的释放。

创建Semaphore的时候会将do_vtable指向_dispatch_semaphore_vtable,_dispatch_semaphore_vtable的结构定义了信号量销毁的时候会执行_dispatch_semaphore_dispose方法,相关代码实现如下

//semaphore的vtable定义
DISPATCH_VTABLE_INSTANCE(semaphore,
    .do_type = DISPATCH_SEMAPHORE_TYPE,
    .do_kind = "semaphore",
    .do_dispose = _dispatch_semaphore_dispose,  //销毁时执行的回调函数
    .do_debug = _dispatch_semaphore_debug,      //debug函数
);

//释放信号量的函数
void _dispatch_semaphore_dispose(dispatch_object_t dou) {
    dispatch_semaphore_t dsema = dou._dsema;

    if (dsema->dsema_value < dsema->dsema_orig) {
       //Warning:信号量还在使用的时候销毁会造成崩溃
        DISPATCH_CLIENT_CRASH(
                "Semaphore/group object deallocated while in use");
    }
    kern_return_t kr;
    if (dsema->dsema_port) {
        kr = semaphore_destroy(mach_task_self(), dsema->dsema_port);
        DISPATCH_SEMAPHORE_VERIFY_KR(kr);
    }
}

如果销毁时信号量还在使用,那么dsema_value会小于dsema_orig,则会引起崩溃,这是一个特别需要注意的地方。这里模拟一下信号量崩溃的代码:

dispatch_semaphore_t semephore = dispatch_semaphore_create(1);
dispatch_semaphore_wait(semephore, DISPATCH_TIME_FOREVER);
//重新赋值或者将semephore = nil都会造成崩溃,因为此时信号量还在使用中
semephore = dispatch_semaphore_create(0);

dispatch_semaphore_wait
long dispatch_semaphore_wait(dispatch_semaphore_t dsema, dispatch_time_t timeout){
    long value = dispatch_atomic_dec2o(dsema, dsema_value, acquire);
    if (fastpath(value >= 0)) {
        return 0;
    }
    return _dispatch_semaphore_wait_slow(dsema, timeout);
}

dispatch_semaphore_wait先将信号量的dsema值原子性减一,并将新值赋给value。如果value大于等于0就立即返回,否则调用_dispatch_semaphore_wait_slow函数,等待信号量唤醒或者timeout超时。_dispatch_semaphore_wait_slow函数定义如下:

static long _dispatch_semaphore_wait_slow(dispatch_semaphore_t dsema,
        dispatch_time_t timeout) {
    long orig;
    mach_timespec_t _timeout;
    kern_return_t kr;
again:
    orig = dsema->dsema_sent_ksignals;
    while (orig) {
        if (dispatch_atomic_cmpxchgvw2o(dsema, dsema_sent_ksignals, orig,
                orig - 1, &orig, relaxed)) {
            return 0;
        }
    }

    _dispatch_semaphore_create_port(&dsema->dsema_port);
    switch (timeout) {
    default:
    do {
            uint64_t nsec = _dispatch_timeout(timeout);
            _timeout.tv_sec = (typeof(_timeout.tv_sec))(nsec / NSEC_PER_SEC);
            _timeout.tv_nsec = (typeof(_timeout.tv_nsec))(nsec % NSEC_PER_SEC);
            kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));
        } while (kr == KERN_ABORTED);

        if (kr != KERN_OPERATION_TIMED_OUT) {
            DISPATCH_SEMAPHORE_VERIFY_KR(kr);
            break;
        }
    case DISPATCH_TIME_NOW:
        orig = dsema->dsema_value;
        while (orig < 0) {
            if (dispatch_atomic_cmpxchgvw2o(dsema, dsema_value, orig, orig + 1,
                    &orig, relaxed)) {
                return KERN_OPERATION_TIMED_OUT;
            }
        }
    case DISPATCH_TIME_FOREVER:
    do {
            kr = semaphore_wait(dsema->dsema_port);
        } while (kr == KERN_ABORTED);
        DISPATCH_SEMAPHORE_VERIFY_KR(kr);
        break;
    }
    goto again;
}

_dispatch_semaphore_wait_slow函数根据timeout的类型分成了三种情况处理:

1、DISPATCH_TIME_NOW:若desma_value小于0,对其加一并返回超时信号KERN_OPERATION_TIMED_OUT,原子性加一是为了抵消dispatch_semaphore_wait函数开始的减一操作。
2、DISPATCH_TIME_FOREVER:调用系统的semaphore_wait方法,直到收到signal调用。

kr = semaphore_wait(dsema->dsema_port);

3、default:调用内核方法semaphore_timedwait计时等待,直到有信号到来或者超时了。

kr = slowpath(semaphore_timedwait(dsema->dsema_port, _timeout));

dispatch_semaphore_wait的流程图可以用下图表示:

dispatch_semaphore_wait.png
dispatch_semaphore_signal
long dispatch_semaphore_signal(dispatch_semaphore_t dsema) {
    long value = dispatch_atomic_inc2o(dsema, dsema_value, release);
    if (fastpath(value > 0)) {
        return 0;
    }
    if (slowpath(value == LONG_MIN)) {
       //Warning:value值有误会造成崩溃,详见下篇dispatch_group的分析
        DISPATCH_CLIENT_CRASH("Unbalanced call to dispatch_semaphore_signal()");
    }
    return _dispatch_semaphore_signal_slow(dsema);
}

首先将dsema_value调用原子方法加1,如果大于零就立即返回0,否则进入_dispatch_semaphore_signal_slow方法,该函数会调用内核的semaphore_signal函数唤醒在dispatch_semaphore_wait中等待的线程。代码如下:

long _dispatch_semaphore_signal_slow(dispatch_semaphore_t dsema) {
    _dispatch_retain(dsema);
    (void)dispatch_atomic_inc2o(dsema, dsema_sent_ksignals, relaxed);
    _dispatch_semaphore_create_port(&dsema->dsema_port);
    kern_return_t kr = semaphore_signal(dsema->dsema_port);
    DISPATCH_SEMAPHORE_VERIFY_KR(kr);

    _dispatch_release(dsema);
    return 1;
}

dispatch_semaphore_signal的流程比较简单,可以用下图表示:

dispatch_semaphore_signal.png

Dispatch Semaphore信号量主要是dispatch_semaphore_wait和dispatch_semaphore_signal函数,wait会将信号量值减一,如果大于等于0就立即返回,否则等待信号量唤醒或者超时;signal会将信号量值加一,如果value大于0立即返回,否则唤醒某个等待中的线程。

需要注意的是信号量在销毁或重新创建的时候如果还在使用则会引起崩溃,详见上面的分析。

延迟执行:dispatch_after

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{

        });

注意:示例中dispatch_after的真正含义是在5秒后把任务添加进队列中,并不是表示在5秒后执行,考虑到队列阻塞等情况,所以这个任务从加入队列到真正执行的时间是不准确的。

dispatch_once

dispatch_once函数是保证在应用程序执行中只执行一次指定处理的API。即使在多线程环境下执行,也可以百分之百安全。常用于创建单例。
示例:

@interface CommonRecord()

@end

@implementation CommonRecord

//全局变量
static CommonRecord *_sharedCommonRecord = nil;
static dispatch_once_t once_t;
//alloc会调用allocWithZone:
+(instancetype)allocWithZone:(struct _NSZone *)zone{
    //只进行一次
    
    dispatch_once(&once_t, ^{
        _sharedCommonRecord = [super allocWithZone:zone];
    });
    return _sharedCommonRecord;
}
+ (instancetype)sharedInstance {
    dispatch_once(&once_t, ^{
        _sharedCommonRecord = [[super allocWithZone:NULL] init];
    });
    return _sharedCommonRecord;
}
//copy在底层 会调用copyWithZone:
- (id)copyWithZone:(NSZone *)zone{
    return  _sharedCommonRecord;
}
- (id)mutableCopyWithZone:(NSZone *)zone{
    return _sharedCommonRecord;
}
- (instancetype)init {
    self = [super init];
    if (self) {

    }
    return self;
}
@end

dispatch_release & dispatch_retain

sdk6.0以前不管arc或者none-arc都需要手动retain和release GCD 对象。
sdk6.0以后GCD对象可以有arc处理。不需要手动retain和release。

#if !OS_OBJECT_USE_OBJC   //这个宏是在sdk6.0之后才有的,如果是之前的,则OS_OBJECT_USE_OBJC为0

dispatch_retain(sq);
dispatch_release(acceptSource);

#endif

  • 参考资料:
    dispatch_barrier_sync 和dispatch_barrier_async的区别
    dispatch_barrier_sync和dispatch_barrier_async
    GCD源码分析5 —— dispatch_semaphore篇

你可能感兴趣的:(iOS GCD简介(二))