多线程之GCD

GCD术语

  • 串行和并行
  • 同步和异步
  • 关键段
  • 竞争条件
  • 死锁
  • 线程安全
  • 线程上下文切换

平行与并发

  • 平行:多个线程可以同时执行
  • 并发:如果是多核的则多个线程可以同时执行,但是如果是单核的,就会涉及到线程上下文切换,足够快的切换,可以造成一种假象就是在同时执行。

分发队列

GCD提供了分发队列去处理代码块,这些任务会以先进先出的方式执行。所有的分发队列自己是线程安全的,你可以同时从不同的线程去获取他们。下面会介绍两种GCD提供的特殊队列:

线性队列

在线性队列中的任务一次只执行一个,每个任务只在上个任务执行完成后开始执行,你不能确保每个代码块执行的时间,但是这些任务会按照添加到队列中的顺序进行执行。在一个线性队列的两个任务不可能同时执行。

并发队列

并发队列中的任务会以添加到队列中的顺序进行执行,但是你不能确保他们以何种顺序结束。所以你无法确保同一时间有多少个任务正在执行。

队列类型

首先,系统提供了一个叫做“main queue”的特殊线性队列,只可以在这个线程中更新你的UI,这个队列用来向UIViews发送消息或者发布通知。系统也提供了几个并发队列-“Global Dispatch Queues”,它们是四种拥有不同优先级的全局队列,优先级为“background, low, default, high”。应该意识到Appple's API也在使用这些队列,所以你添加到这些队列的任何任务不是这些队列中的唯一任务。 最后,你可以创建你自己的线性或者并发队列。这意味着你至少有五种队列:主队列,四种全局分发队列,加之你可以自定义的队列。

  • 主队列:dispatch_get_main_queue()
  • 全局队列: dispatch_global_queue(优先级选项)
    优先级:
    • DISPATCH_QUEUE_PRIORITY_HIGH
    • DISPATCH_QUEUE_PRIORITY_LOW
    • DISPATCH_QUEUE_PRIORITY_BACKGROUND
    • DISPATCH_QUEUE_PRIORITY_DEFAULT

diapatch_async

dispatch_async在一个队列中增加一个block并且立即返回。在block中的task将在之后特定的时间被执行。在执行基于网络的或者cpu密集型任务的时候应该使用dispatch_async在后台执行。

不同队列类型的使用指南

  • 自定义的线性队列:当你想线性执行后台任务,并且跟踪此任务的的时候。-dispatch_sync
  • 主队列:更新UI
  • 并发队列:在后台执行非UI工作。

使用dispatch_after延迟工作

在延迟的一段时间后再执行。

double delayInSeconds = 1.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_MSEC));

dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
    if (!count) {
        [self.navigationItem setPrompt:@"Add photos with faces to Googlyiy them!"];
    } else {
        [self.navigationItem setPrompt:nil];
    }
});

什么时候适合用dispatch_after:

  • Main Queue

在使用单例模式时要注意线程安全

单例经常在同一时间被多个控制器使用。

为保证单例初始化代码一次只执行一个,可以使用dispatch_once

+ (instancetype)sharedManager
{
    static PhotoManager *sharedPhotoManager = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        sharedPhotoManager = [[PhotoManager alloc] init];
        sharedPhotoManager->_photosArray = [NSMutableArray array];
    });
    return sharedPhotoManager;
}

dispatch_once()以线程安全的方式一次只执行一个block。
注:以上的代码只是保证以线程安全的方式获取共享实例,没有使类线程安全。

读者写者问题

许多可变的对象,如NSMutableArray,不能保证在一个线程读的时候,其他线程不能写。
GCD提供了一种优雅的解决方式:读写锁 dispatch barriers

读者:

- (void)addPhoto:(Photo *)photo
{
    if (photo) { // 1
        dispatch_barrier_async(self.concurrentPhotoQueue, ^{ // 2 
            [_photosArray addObject:photo]; // 3
            dispatch_async(dispatch_get_main_queue(), ^{ // 4
                [self postContentAddedNotification]; 
            });
        });
    }
}

什么时候适合用dispatch barriers

  • 自定义的同步队列 custom concurrent queue

写者:为确保读者的线程安全,写者要和读者在同一个分发队列中,使用dispatch_sync去等待读完成。

什么时候适合用dispatch_sync

  • 自定义的同步队列 custom concurrent queue

eg:

- (NSArray *)photos
{
    __block NSArray *array; // 1
    dispatch_sync(self.concurrentPhotoQueue, ^{ // 2
        array = [NSArray arrayWithArray:_photosArray]; // 3
    });
    return array;
}

Dispatch Groups

Dispatch groups在一组task全部完成之后通知你。这些任务可以是同步或者异步的甚至在不同队列中。

在组里的所有事件完成的时候,GCD API提供了两种两种方式进行通知。

  • dispatch_group_wait 这个函数会阻塞你当前的线程直到在组里的所有任务都完成之后。
    相关方法:

    dispatch_group_t downloadGroup = dispatch_group_create(); 创建一个group

    dispatch_group_enter(downloadGroup); 进入

    dispatch_group_leave(downloadGroup); 离开

    dispatch_group_wait(downloadGroup, DISPATCH_TIME_FOREVER); 等待

  • dispatch_group_notify 这个函数不会阻塞你当前的线程,在组里的所有任务完成之后,它会异步的通知你。

Dispatch_apply

dispatch_apply扮演了for循环的角色,它会同时执行循环中的各个条件。

什么时候适合用dispatch_apply

  • 自定义的线性队列, 同步队列

eg:

  dispatch_apply(3, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^(size_t i) {

    NSURL *url;
    switch (i) {
        case 0:
            url = [NSURL URLWithString:kOverlyAttachedGirlfriendURLString];
            break;
        case 1:
            url = [NSURL URLWithString:kSuccessKidURLString];
            break;
        case 2:
            url = [NSURL URLWithString:kLotsOfFacesURLString];
            break;
        default:
            break;
    }

    dispatch_group_enter(downloadGroup);
    Photo *photo = [[Photo alloc] initwithURL:url
                          withCompletionBlock:^(UIImage *image, NSError *_error) {
                              if (_error) {
                                  error = _error;
                              }
                              dispatch_group_leave(downloadGroup);
                          }];

    [[PhotoManager sharedManager] addPhoto:photo];
});     

参考文档:

  • http://www.raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1
  • http://www.raywenderlich.com/63338/grand-central-dispatch-in-depth-part-2

你可能感兴趣的:(多线程之GCD)