GCD学习

2017-03-09  本文已影响24人  勇往直前888

多线程技术,首选的是NSOperation,在有些场合,考虑直接用GCD。c语言的风格,block的调用方式,更加灵活可控。
由于苹果推荐说用GCD,不过这是c风格了,跟Object-C这种面向对象的风格不是很符合。还是推荐NSOperation为主。当然,方便的时候,用用简单的经典的GCD也无妨。AFNetworking就是将NSOperationGCD结合起来用的,比较经典。
GCD中一些类型的定义在系统的如下路径
/usr/include/dispatch/

下面这两篇文章比较好地解释了同步异步,串行并行的概念,可以参考一下。虽然文章名字差不多,不过真的是不一样的,而且写得都还不错。
iOS多线程与GCD 你看我就够了
关于iOS多线程,你看我就够了

同步/异步(执行)

差异点在于是否阻塞当前的调用者线程,等待队列中的任务执行完毕。

同步执行

typedef void (^dispatch_block_t)(void);

dispatch_sync(dispatch_queue_t queue, DISPATCH_NOESCAPE dispatch_block_t block);
NSLog(@"之前 - %@", [NSThread currentThread]);
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"sync - %@", [NSThread currentThread]);
});
NSLog(@"之后 - %@", [NSThread currentThread]);

异步执行

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

格式基本和同步的差不多,就一个单词的差别,但是表现完全两样。上面的例子用这个函数的话,就不会“崩溃或者死机”。其输出一般情况是这样的:

之前 - 
之后 - 
sync - 

串行/并行(队列)

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

第一个参数是队列的名字,是个字符串;
第二个参数决定队列的性质,串行或者并行;
虽然有ARC,但生成的Dispatch Queue必须由程序员主动释放。

dispatch_release(exampleSerialDispatchQueue);  // 释放
dispatch_retain(exampleSerialDispatchQueue);   // 持有

串行队列

ispatch_queue_t dispatch_get_main_queue(void) {
    return DISPATCH_GLOBAL_OBJECT(dispatch_queue_t, _dispatch_main_q);
}

并行队列

dispatch_queue_t dispatch_get_global_queue(long identifier, unsigned long flags);

#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

第一个参数表示队列的优先级,一般用DISPATCH_QUEUE_PRIORITY_DEFAULT;第2个参数基本上是0

单例

+ (instancetype)sharedManager {
    static AFNetworkReachabilityManager *_sharedManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        struct sockaddr_in address;
        bzero(&address, sizeof(address));
        address.sin_len = sizeof(address);
        address.sin_family = AF_INET;

        _sharedManager = [self managerForAddress:&address];
    });

    return _sharedManager;
}

延时执行

void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
dispatch_time_t dispatch_time(dispatch_time_t when, int64_t delta);

typedef uint64_t dispatch_time_t;
#define DISPATCH_TIME_NOW (0ull)
#define DISPATCH_TIME_FOREVER (~0ull)

上面就是常用的函数和相应的配套函数和参数。第1个参数是时间,一般用第2个函数来获得,名字看上去都差不多。最小单位是纳秒,所以一般时间要用到下面几个常数定义。这个延时比NSTimer要精确一点,很多第3方库都在用,可以作为经典形式。
下面就是YYCache中的一个实际的例子

- (void)_trimRecursively {
    __weak typeof(self) _self = self;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
        __strong typeof(_self) self = _self;
        if (!self) return;
        [self _trimInBackground];
        [self _trimRecursively];
    });
}

_autoTrimInterval就是要延迟执行的秒数

Group操作

有时候我们会有这种需求,在刚进去一个页面需要发送两个请求,并且更新页面操作必须在两个请求都结束(成功或失败)的时候才会执行。
第1种方式是用串行队列,两个网络请求依次进行,然后更新界面。这样做时序没有问题,不过当这两个网络请求之间没有相互依赖的话,效率损失比较大。
第2种方式是用NSOperationQueue,将最后的更新页面操作依赖前面2个网络操作就可以了,其他的事交给系统。这种是推荐的方式
第3种是用GCDdispatch_group函数族,可以获得更高的性能,更灵活地控制。这种方式不推荐,不过在AFNetworking中有比较大量的应用,所以也不反对使用。

dispatch_group_t dispatch_group_create(void);
void dispatch_group_enter(dispatch_group_t group);
void dispatch_group_async(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
long dispatch_group_wait(dispatch_group_t group, dispatch_time_t timeout);
void dispatch_group_notify(dispatch_group_t group, dispatch_queue_t queue, dispatch_block_t block);
void dispatch_group_leave(dispatch_group_t group);

dispatch_group_wait比较特殊,正常情况下不需要用到。比如,在这个例子中,两个网络请求之后就更新界面了。但是如果网络请求因为某种原因长时间不返回呢(这种情况还是蛮多的)?那就设一个超时时间吧。比如,最多等30s,如果还不返回,就显示网络不给力了。这个函数是同步的,傻等,结果看返回值,0表示成功。常见的超时等待处理方式。这种能力NSOperationQueue是没有的。

代码参考格式:

dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
dispatch_async(dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //在这里执行异步请求A
    并且在执行结束代码(成功或失败)中写上dispatch_group_leave(group);
});
dispatch_async(dispath_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //在这里执行异步请求B
    并且在执行结束代码(成功或失败)中写上dispatch_group_leave(group);
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    //在这里执行异步请求B
});

NSOperationQueue复杂不了多少。

关于超时的参考格式:

#define kTimeOut   30ull 

dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, (kTimeOut * NESC_PER_SEC));
long result = dispatch_group_wait(group, time);
if(0 == result) { 
    // 在限定时间内返回;dispatch_group_notify执行,正常情况;啥也不用做,当然也可以log一下
} else { 
    // 超时了,任务还没有全部完成,dispatch_group_notify这时还没有执行
    // 如果这里显示超时,网络请求并不能取消,这个有点尴尬,dispatch_group_notify还是有可能执行的,只是观众不愿意等了
};

一些扫尾的工作

在文件object.h中有一些函数定义:

void dispatch_suspend(dispatch_object_t object);
void dispatch_resume(dispatch_object_t object);
void dispatch_cancel(void *object);
void dispatch_release(dispatch_object_t object);

栅栏函数(这部分内容有点多,Option)

// 异步栅栏,比较常用
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block);

// 同步栅栏,会阻塞当前的调用者线程,
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block);

栅栏函数可以看做是dispatch_group的升级版。就像一个分界点,将队列一分为二,前后两部分有顺序依赖。可以简单的认为:

  1. 执行栅栏函数之前加入队列的任务;等待,直到任务全部执行完毕
  2. 执行栅栏函数添加到队列中的任务;
  3. 等待栅栏函数添加的任务,直到任务全部执行完毕。
  4. 执行栅栏函数之后添加的任务

下面这篇文章写得很不错,值得好好看看。以前一直讨厌这个栅栏,这次算有点理解了,开始喜欢这个栅栏函数:
通过GCD中的dispatch_barrier_(a)sync加强对sync中所谓等待的理解

实际的例子

- (void)insertObject:(id)anObject atIndex:(NSUInteger)index {
    dispatch_barrier_async(_queue, ^{
        [_array insertObject:anObject atIndex:index];
    });
}

自己写的一个例子

case1: 异步栅栏在前,同步栅栏在后

这种情况,将有可能影响当前线程(主线程)的同步操作放在后面,对当前线程的影响最小,相对好理解一点。

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
    // 异步执行
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_async 2");
    });
    
    // 栅栏异步执行
    dispatch_barrier_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_async");
    });
    NSLog(@"main thread after dispatch_barrier_async");
    
    // 栅栏同步执行
    dispatch_barrier_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_sync");
    });
    NSLog(@"main thread after dispatch_barrier_sync");
    
    // 同步执行
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_sync 3");
    });
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_sync 4");
    });
}

输出的log:

2017-03-09 17:09:25.573 Barrier[82252:5039848] main thread after dispatch_barrier_async
2017-03-09 17:09:26.141 Barrier[82252:5039953] dispatch_async 2
2017-03-09 17:09:26.639 Barrier[82252:5039954] dispatch_async 1
2017-03-09 17:09:26.639 Barrier[82252:5039954] dispatch_barrier_async
2017-03-09 17:09:26.640 Barrier[82252:5039848] dispatch_barrier_sync
2017-03-09 17:09:26.640 Barrier[82252:5039848] main thread after dispatch_barrier_sync
2017-03-09 17:09:27.715 Barrier[82252:5039848] dispatch_sync 3
2017-03-09 17:09:28.289 Barrier[82252:5039848] dispatch_sync 4

过程分析:

  1. main thread执行到dispatch_barrier_sync, 被阻塞,等待。这个时候,main thread after dispatch_barrier_async已经输出
  2. 任务1和2异步执行,逆序输出(2的任务时间短)
  3. 异步栅栏执行,输出dispatch_barrier_async
  4. 同步栅栏执行,输出dispatch_barrier_sync
  5. main thread被唤醒,继续执行,输出main thread after dispatch_barrier_sync
  6. main thread执行到dispatch_sync 3, 被阻塞,等待。
  7. 任务3,4同步执行,顺序输出(4的任务时间短也没用,要等3完成才能执行)
  8. main thread被唤醒,继续执行,碰到}返回,过程结束
case2: 同步栅栏在前,异步栅栏在后

这里主要看栅栏同步异步的影响

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
    // 异步执行
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_async 2");
    });
    
    // 栅栏同步执行
    dispatch_barrier_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_sync");
    });
    NSLog(@"main thread after dispatch_barrier_sync");
    
    // 栅栏异步执行
    dispatch_barrier_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_async");
    });
    NSLog(@"main thread after dispatch_barrier_async");
    
    // 同步执行
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_sync 3");
    });
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_sync 4");
    });
}

输出的log:

2017-03-09 20:59:47.526 Barrier[91896:5120619] dispatch_async 2
2017-03-09 20:59:48.027 Barrier[91896:5120621] dispatch_async 1
2017-03-09 20:59:48.528 Barrier[91896:5120438] dispatch_barrier_sync
2017-03-09 20:59:48.528 Barrier[91896:5120438] main thread after dispatch_barrier_sync
2017-03-09 20:59:48.529 Barrier[91896:5120438] main thread after dispatch_barrier_async
2017-03-09 20:59:49.029 Barrier[91896:5120621] dispatch_barrier_async
2017-03-09 20:59:50.051 Barrier[91896:5120438] dispatch_sync 3
2017-03-09 20:59:50.626 Barrier[91896:5120438] dispatch_sync 4

过程分析:

  1. main thread执行到dispatch_barrier_sync, 被阻塞,等待。这个时候,啥也没有输出
  2. 任务1和2异步执行,逆序输出(2的任务时间短)
  3. 同步栅栏执行,输出dispatch_barrier_sync
  4. main thread执行到dispatch_sync 3, 被阻塞,等待。这段时间main thread做的事情有:(a)main thread after dispatch_barrier_sync输出;(b)异步栅栏被分配,但是main thread没有被阻塞;(c)main thread after dispatch_barrier_async输出;
  5. 异步栅栏执行,输出dispatch_barrier_async
  6. 任务3,4同步执行,顺序输出(4的任务时间短也没用,要等3完成才能执行)
  7. main thread被唤醒,继续执行,碰到}返回,过程结束
case3: 同步过程在前,异步过程在后

这个例子,调用者线程(这里是main thread)分配任务之后先于工作者线程返回,是大多数要用到的情况。

代码:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    dispatch_queue_t currentQueue = dispatch_queue_create("currentQueue", DISPATCH_QUEUE_CONCURRENT);
    
    // 同步执行
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_sync 3");
    });
    dispatch_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_sync 4");
    });
    
    // 栅栏同步执行
    dispatch_barrier_sync(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_sync");
    });
    NSLog(@"main thread after dispatch_barrier_sync");
    
    // 栅栏异步执行
    dispatch_barrier_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_barrier_async");
    });
    NSLog(@"main thread after dispatch_barrier_async");
    
    // 异步执行
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"dispatch_async 1");
    });
    dispatch_async(currentQueue, ^{
        [NSThread sleepForTimeInterval:0.5];
        NSLog(@"dispatch_async 2");
    });
}

输出的log:

2017-03-09 21:08:39.715 Barrier[92752:5127802] dispatch_sync 3
2017-03-09 21:08:40.285 Barrier[92752:5127802] dispatch_sync 4
2017-03-09 21:08:40.856 Barrier[92752:5127802] dispatch_barrier_sync
2017-03-09 21:08:40.856 Barrier[92752:5127802] main thread after dispatch_barrier_sync
2017-03-09 21:08:40.856 Barrier[92752:5127802] main thread after dispatch_barrier_async
2017-03-09 21:08:41.430 Barrier[92752:5127902] dispatch_barrier_async
2017-03-09 21:08:41.999 Barrier[92752:5127901] dispatch_async 2
2017-03-09 21:08:42.499 Barrier[92752:5127902] dispatch_async 1

过程分析:

  1. 任务3,4同步执行,顺序输出(4的任务时间短也没用,要等3完成才能执行)
  2. 同步栅栏执行,输出dispatch_barrier_sync
  3. 这段时间main thread一直被阻塞,直到同步栅栏执行完毕
  4. main thread被唤醒,继续执行,碰到}返回,main thread的过程结束。这段时间main thread做的事情有:(a)main thread after dispatch_barrier_sync输出;(b)异步栅栏被分配,但是main thread没有被阻塞;(c)main thread after dispatch_barrier_async输出;(d)任务1和2被分配,但是main thread没有被阻塞;
  5. 异步栅栏执行,输出dispatch_barrier_async
  6. 任务1和2异步执行,逆序输出(2的任务时间短)

小结

上一篇下一篇

猜你喜欢

热点阅读