多线程之GCD

作者: wright | 来源:发表于2015-03-26 10:00 被阅读208次

    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];
    });     
    

    参考文档:

    相关文章

      网友评论

        本文标题:多线程之GCD

        本文链接:https://www.haomeiwen.com/subject/vxzgxttx.html