学习GCD

作者: 江户川_乱步 | 来源:发表于2016-08-18 11:13 被阅读152次

    GCD(Grand Central Dispatch)介绍

    GCD是苹果为开发者提供的系统级别的线程管理api,开发过程中专门对多核心性能做了优化,所以在dispatch queue中执行任务性能很高。

    GCD概要

    • 和operation queue一样都是基于队列的并发编程API,底层使用的是线程池的技术,集中管理大家协同使用的线程池。
    • 系统提供五个不同的全局队列
      • main queue 主线程队列,也是UI更新的线程
      • 四个不同优先级的队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue(用于I/O)
      • 可创建自定义队列:串行或并列队列。自定义队列一般放在Default Priority Queue和Main Queue里。
      • 操作是在多线程上还是单线程主要是看队列的类型和执行方法,并行队列异步执行才能在多线程,并行队列同步执行就只会在主线程执行了

    基本概念

    • 系统提供的两个标准队列
    //全局队列,是一个并行队列,可以设置不同的优先级
    dispatch_get_globle_queue
    //主队列,主线程中的唯一队列,是一个串行队列
    dispatch_get_main_queue
    
    • 自定义队列
    //串行队列
    dispatch_queue_create("com.fineway.serialqueue",DISPATCH_QUEUE_SERIAL);
    //并行队列
    dispatch_queue_create("com.fineway.coucurrent",DISPATCH_QUEUE_CONCURRENT);
    
    • 同步异步线程的创建
    //同步线程
    dispatch_sync(..., ^(block))
    //异步线程
    dispatch_async(..., ^(block))
    

    队列(dispatch queue)

    • Serial:又叫private dispatch queues,同时只执行一个任务。Serial queue常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然各自是同步,但serial queue之间是并发执行。
    • Main dispatch queue:全局可用的serial queue,在应用程序主线程上执行任务。
    • Concurrent:又叫global dispatch queue,可以并发的执行多个任务,但执行完成顺序是随机的。系统提供四个全局并发队列,这四个队列有着对应的优先级,用户是不能够创建全局队列的,只能获取。
    dispatch_queue_t queue;
    queue = dispatch_get_globle_queue( DISPATCH_QUEUE_PEIORITY_HIGH,0);
    
    • user create queue:创建自己定义的队列,可以用dispatch_queue_create函数,函数有两个参数,第一个自定义的队列名,第二个参数是队列类型,默认NULL或者DISPATCH_QUEUE_SERIAL的是串行,参数为DISPATCH_QUEUE_CONCURRENT为并行队列。
    dispatch_queue_t queue ;
    queue = dispatch_queue_create("com.fineway.concurrent",
    DISPATCH_QUEUEU_CONCURRENT);
    
    • 自定义队列的优先级:可以通过dipatch_queue_attr_make_with_qos_class或dispatch_set_target_queue方法设置队列的优先级
    //dipatch_queue_attr_make_with_qos_class
    dispatch_queue_attr_t attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, -1);
    dispatch_queue_t queue = dispatch_queue_create("com.fineway.serialqueue", attr);
    //dispatch_set_target_queue
    dispatch_queue_t queue = dispatch_queue_create("com.fineway.serialqueue",NULL); //需要设置优先级的queue
    dispatch_queue_t referQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0); //参考优先级
    dispatch_set_target_queue(queue, referQueue); //设置queue和referQueue的优先级一样
    
    • dispatch_set_target_queue:可以设置优先级,也可以设置队列层级体系,比如让多个串行和并行队列在统一一个串行队列里串行执行,如下
    dispatch_queue_t serialQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t firstQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t secondQueue = dispatch_queue_create("com.fineway.serialqueue", DISPATCH_QUEUE_CONCURRENT);
    dispatch_set_target_queue(firstQueue, serialQueue);
    dispatch_set_target_queue(secondQueue, serialQueue);
    
      dispatch_async(firstQueue, ^{ 
      NSLog(@"1"); 
      [NSThread sleepForTimeInterval:3.f];
    });
    dispatch_async(secondQueue, ^{ 
      NSLog(@"2"); 
      [NSThread sleepForTimeInterval:2.f];
    });
    dispatch_async(secondQueue, ^{ 
      NSLog(@"3"); 
      [NSThread sleepForTimeInterval:1.f];
    });
    

    队列类型

    队列类型默认是串行的,如果创建队列的时候参数是NULL,队列是串行的,队列参数是DISPATCH_QUEUE_CONCURRENT时,队列是并行的,可以同时执行多个block。

    • 何时使用何种队列类型
      • 主队列(顺序):队列中有任务完成需要更新UI时,dispatch_after在这种类型中使用。
      • 并发队列:用来执行与UI无关的后台任务,dispatch_sync放在这里,方便等待任务完成进行后续处理或和dispatch barrier同步。dispatch groups放在这里也不错。
      • 自定义顺序队列:顺序执行后台任务并追踪它时。这样做同时只有一个任务在执行可以防止资源竞争。dipatch barriers解决读写锁问题的放在这里处理。dispatch groups也是放在这里。

    关于dispatch_once

    使用dispatch_once创建单例时,要保证dispatch_once_t是全局或者static变量,保证只是创建一次。

    + (AppDataSource *)shareAppDataSource
    {
        static AppDataSource *shareAppDataSource = nil;
        static dispatch_once_t predicate;
        dispatch_once(&predicate, ^{
            shareAppDataSource = [[self alloc] init];
        });
        return shareAppDataSource;
    }
    

    关于dispatch_async

    异步执行api,将任务切换到工作线程中执行,避免阻塞主线程。比如读取网络数据,大数据IO,还有大量数据的数据库读写,这时需要在另一个线程中处理,然后通知主线程更新界面,GCD使用起来比NSThread和NSOperation方法要简单方便。

    //代码框架,异步执行,完成后主线程更新UI
    dispatch_async(dispatch_get_globle_queue(), ^(void){ 
      // do actual processing here 
      dispatch_async(dispatch_get_main_queue(), ^(void){
         handler(YES);
       }); 
    });
    

    下载图片示例

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 
      NSURL * url = [NSURL URLWithString:@"http://avatar.csdn.net/2/C/D/1_totogo2010.jpg"]; 
      NSData * data = [[NSData alloc]initWithContentsOfURL:url];  
      UIImage *image = [[UIImage alloc]initWithData:data]; 
      if (data != nil) { 
        dispatch_async(dispatch_get_main_queue(), ^{ 
          self.imageView.image = image;
         });
       }
    });
    

    关于dispatch_after延后执行

    延时提交block,不等同与延时立即执行。介绍dispatch_after之前先了解一下dispatch time.

    dispatch_time_t dispatch_time ( dispatch_time_t when, int64_t delta );
    
    • 第一个参数表示从什么时间开始,DISPATCH_TIME_NOW表示从当前开始
    • 第二个参数表示延时的时间,单文是纳秒,一秒钟等于1000000000纳秒,系统提供一些宏定义来简化计算
    #define NSEC_PER_SEC 1000000000ull //每秒有多少纳秒 
    #define USEC_PER_SEC 1000000ull //每秒有多少毫秒 
    #define NSEC_PER_USEC 1000ull //每毫秒有多少纳秒
    

    如果想要延时一秒,可以这样写

    dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC);
    dispatch_time(DISPATCH_TIME_NOW, 1000 * USEC_PER_SEC);
    dispatch_time(DISPATCH_TIME_NOW, USEC_PER_SEC * NSEC_PER_USEC);
    

    延时示例实现

    double delayInSeconds = 2.0; //延时时间
    dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC)); //从现在起延时delayInSeconds执行
    dispatch_after(popTime, dispatch_get_main_queue(), ^(void){     
        [self bar]; 
    });//主线程延迟2s后执行bar方法。
    

    关于dispatch_barrier_async

    Dispatch Barrier确保提交的闭包是指定队列中在特定时段唯一在执行的一个。在所有先于Dispatch Barrier的任务都完成的情况下这个闭包才开始执行。轮到这个闭包时barrier会执行这个闭包并且确保队列在此过程不会执行其它任务。闭包完成后队列恢复。需要注意dispatch_barrier_async只在自己创建的队列上有这种作用,在全局并发队列和串行队列上,效果和dispatch_sync一样。

    赋值示例

    self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
    
    - (void)setCount:(NSUInteger)count forKey:(NSString *)key{ 
      key = [key copy]; 
      //确保所有barrier都是async异步的   
      dispatch_barrier_async(self.isolationQueue, ^(){ 
        if (count == 0) {
           [self.counts removeObjectForKey:key]; 
        } else { 
          self.counts[key] = @(count); 
        } 
      });
    }
    
    //防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。 
    dispatch_queue_t dataQueue = dispatch_queue_create("com.fineway.dataqueue", DISPATCH_QUEUE_CONCURRENT); 
    dispatch_async(dataQueue, ^{ 
     [NSThread sleepForTimeInterval:2.f]; 
      NSLog(@"read data 1"); 
    }); 
    dispatch_async(dataQueue, ^{ 
      NSLog(@"read data 2"); 
    }); 
    //等待前面的都完成,在执行barrier后面的 
    dispatch_barrier_async(dataQueue, ^{ 
      NSLog(@"write data 1"); 
      [NSThread sleepForTimeInterval:1]; 
    }); 
    dispatch_async(dataQueue, ^{ 
      [NSThread sleepForTimeInterval:1.f]; 
      NSLog(@"read data 3"); 
    }); 
    dispatch_async(dataQueue, ^{ 
      NSLog(@"read data 4"); 
    });
    

    关于dispatch_apply

    功能:把一项任务提交到队列中多次执行,具体是并行执行还是串行执行由队列本身决定.注意,dispatch_apply不会立刻返回,在执行完毕后才会返回,是同步的调用。

    for (size_t y = 0; y < height; ++y) { 
      for (size_t x = 0; x < width; ++x) { 
        // Do something with x and y here 
      }
    }
    //因为可以并行执行,所以使用dispatch_apply可以运行的更快- 
    (void)dispatchApplyDemo { dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue", DISPATCH_QUEUE_CONCURRENT); 
      dispatch_apply(10, concurrentQueue, ^(size_t i) { 
        NSLog(@"%zu",i); 
      }); 
    NSLog(@"The end"); //这里有个需要注意的是,dispatch_apply这个是会阻塞当前线程的。这个log打印会在dispatch_apply都结束后才开始执行
    }
    

    dispatch_apply能够避免线程爆炸,因为GCD能够进行线程优化

    - (void)dealWiththreadWithMaybeExplode:(BOOL)explode {   
      dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); 
      if (explode) { 
      //有问题的情况,可能会死锁 
      for (int i = 0; i < 999 ; i++) { 
        dispatch_async(concurrentQueue, ^{ 
          NSLog(@"wrong %d",i);
         //do something hard 
        }); 
      } 
      } else { 
      //会优化很多,能够利用GCD管理 
      dispatch_apply(999, concurrentQueue, ^(size_t i){
       NSLog(@"correct %zu",i); 
        //do something hard
       }); 
    }
    }
    

    关于dispatch_group

    dispatch groups是专门用来监视多个异步任务。dispatch_group_t实例用来追踪不同队列中的不同任务。

    当group里所有事件都完成GCD API有两种方式发送通知,第一种是dispatch_group_wait,会阻塞当前进程,等所有任务都完成或等待超时。第二种方法是使用dispatch_group_notify,异步执行闭包,不会阻塞。

    //dispatch_group_notify
    - (void)dispatchGroupNotifyDemo { 
      dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); 
      dispatch_group_t group = dispatch_group_create(); 
      dispatch_group_async(group, concurrentQueue, ^{ 
        NSLog(@"1"); 
      }); 
      dispatch_group_async(group, concurrentQueue, ^{ 
        NSLog(@"2"); 
      }); 
      dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
        NSLog(@"end"); 
      }); 
      NSLog(@"can continue");
      }
    //dispatch_group_wait
    - (void)dispatchGroupWaitDemo { 
      dispatch_queue_t concurrentQueue = dispatch_queue_create("com.fineway.concurrentqueue",DISPATCH_QUEUE_CONCURRENT); 
      dispatch_group_t group = dispatch_group_create();
    //在group中添加队列的block 
      dispatch_group_async(group, concurrentQueue, ^{ 
        [NSThread sleepForTimeInterval:2.f]; 
        NSLog(@"1"); 
      }); 
      dispatch_group_async(group, concurrentQueue, ^{ 
        NSLog(@"2"); 
      }); 
      dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 
      NSLog(@"can continue");
    }
    

    Dispatch IO 文件操作

    dispatch io读取文件的方式类似于下面的方式,多个线程去读取文件的切片数据,对于大的数据文件这样会比单线程要快很多。

    dispatch_async(queue,^{/*read 0-99 bytes*/});
    dispatch_async(queue,^{/*read 100-199 bytes*/});
    dispatch_async(queue,^{/*read 200-299 bytes*/});
    
    • dispatch_io_create:创建dispatch io
    • dispatch_io_set_low_water:指定切割文件大小
    • dispatch_io_read:读取切割的文件然后合并。
      苹果系统日志API里用到了这个技术,可以在这里查看:相关资料
    pipe_q = dispatch_queue_create("PipeQ", NULL);//创建
    pipe_channel = dispatch_io_create(DISPATCH_IO_STREAM, fd, pipe_q, ^(int err){ 
        close(fd);
    });
    *out_fd = fdpair[1];
    //设置切割大小
    dispatch_io_set_low_water(pipe_channel, SIZE_MAX);
    dispatch_io_read(pipe_channel, 0, SIZE_MAX, pipe_q, ^(bool done, dispatch_data_t pipedata, int err){ 
        if (err == 0) { 
            size_t len = dispatch_data_get_size(pipedata); 
            if (len > 0) { 
            //对每次切块数据的处理 
                const char *bytes = NULL; 
                char *encoded; 
                uint32_t eval; 
                dispatch_data_t md = dispatch_data_create_map(pipedata, (const void **)&bytes, &len); 
                encoded = asl_core_encode_buffer(bytes, len); 
                asl_msg_set_key_val(aux, ASL_KEY_AUX_DATA, encoded); 
                free(encoded); 
                eval = _asl_evaluate_send(NULL, (aslmsg)aux, -1); 
                _asl_send_message(NULL, eval, aux, NULL); 
                asl_msg_release(aux); 
                dispatch_release(md); 
            } 
        } 
        if (done) { 
            //semaphore +1使得不需要再等待继续执行下去。 
            dispatch_semaphore_signal(sem); 
            dispatch_release(pipe_channel); 
            dispatch_release(pipe_q); 
        }
    });
    

    Dispatch Source 用GCD监视进程

    Dispatch Source用于监听系统的底层对象,比如文件描述符,Mach端口,信号量等。主要处理的事件如下表
    方法 说明
    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_MEMORYPRESSURE 内存情况
    DISPATCH_SOURCE_TYPE_PROC 进程事件
    DISPATCH_SOURCE_TYPE_READ 读数据
    DISPATCH_SOURCE_TYPE_SIGNAL 信号
    DISPATCH_SOURCE_TYPE_TIMER 定时器
    DISPATCH_SOURCE_TYPE_VNODE 文件系统变化
    DISPATCH_SOURCE_TYPE_WRITE 文件写入

    方法

    • dispatch_source_create:创建dispatch source,创建后会处于挂起状态进行事件接收,需要设置事件处理handler进行事件处理。
    • dispatch_source_set_event_handler:设置事件处理handler
    • dispatch_source_set_cancel_handler:事件取消handler,就是在dispatch source释放前做些清理的事。
    • dispatch_source_cancel:关闭dispatch source,设置的事件处理handler不会被执行,已经执行的事件handler不会取消。

    关于Dispatch Semaphore

    另外一种保证同步的方法。使用dispatch_semaphore_signal加1dispatch_semaphore_wait减1,为0时等待的设置方式来达到线程同步的目的和同步锁一样能够解决资源抢占的问题。

    
        //创建semaphore
        dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSLog(@"start");
            [NSThread sleepForTimeInterval:1.f];
            NSLog(@"semaphore +1");
            dispatch_semaphore_signal(semaphore); //+1 semaphore
        });
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);//知道signal发出后才执行
        NSLog(@"continue");
    

    dispatch_suspend和dispatch_resume挂起和恢复队列

    只能挂起未执行的block,不能挂起已经执行或者正在执行的block

    GCD死锁

    当串行队列里面同步执行该串行队列的时候,就会发生死锁,解决的方法就是将同步的串行队列放到另外一个线程就能够解决。

    //示例1
     NSLog(@"1");
     dispatch_sync(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
            NSLog(@"2");
        });
        NSLog(@"3");
    //只打印1,2和3相互等待,不会打印,形成死锁
    
    //示例2
        dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
        NSLog(@"1");
        dispatch_async(serialQueue, ^{
            NSLog(@"2");
            //串行队列里面同步一个串行队列就会死锁
            dispatch_sync(serialQueue, ^{
                NSLog(@"3");
            });
            NSLog(@"4");
        });
        NSLog(@"5");
    //只打印1 5 2,3与4不会打印
    
    //示例3
            NSLog(@"1");
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                NSLog(@"2");
                //将同步的串行队列放到另外一个线程就能够解决
                dispatch_sync(dispatch_get_main_queue(), ^{
                    NSLog(@"3");
                });
                NSLog(@"4");
            });
            NSLog(@"5");
    
    //示例4
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"1");
            //回到主线程发现死循环后面就没法执行了
            dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"2");
            });
            NSLog(@"3");
        });
        NSLog(@"4");
        //死循环
        while (1) {
            //
        }
    

    相关文章

      网友评论

        本文标题:学习GCD

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