GCD解析

作者: 程序狗 | 来源:发表于2017-12-12 16:57 被阅读3次

    GCD是属于系统级别的线程管理,在Dispatch queue中执行需要执行的任务,性能非常高
    ·基于队列的并发编程API,集中管理大家协同使用的线程池
    ·公开的5个不同队列:运行在主线程的main queue,3个不同优先级的后台队列(High Priority Queue,Default Priority Queue,Low Priority Queue),以及一个优先级更低的后台队列Background Priority Queue (用于I/O)
    ·可创建自定义队列:串行或并行队列。自定义一般放在Default Priority Queue和Main Queue里。
    ·操作是在多线程上还是看队列的类型和执行方法,并行队列异步执行才能在多线程,并列队列同步执行就只会在这个并行队列在队列中被分配的那个线程执行。

    系统的两个队列

    //全局队列,一个并行的队列
    dispatch_get_global_queue
    //主队列,主线程中的唯一队列,一个串行队列
    dispatch_get_main_queue
    

    自定义队列

    //串行队列
    dispatch_queue_create("serialQueue", DISPATCH_QUEUE_SERIAL)
    //并行队列
    dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT)
    

    同步异步线程创建

    //同步线程
    dispatch_sync(..., ^(block))
    //异步线程
    dispatch_async(..., ^(block))
    

    队列

    ·串行队列,同时只执行一个任务
    ·主队列,全局可用的串行队列,在主线程上执行任务
    ·并行队列,可以并发执行多个任务,但是顺序是随机的,系统提供四个全局并发队列,这四个队列有对应的优先级

    dipatch_queue_t queue;
    queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH,0);
    

    ·自定义队列可以使用dispatch_queue_create函数,函数有两个参数,第一个是自定义的队列名,第二个是队列类型,默认NULL。

    dispatch_queue_t queue
    queue = dispatch_queue_create("concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    

    ·自定义队列的优先级,可以通过dispatch_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("serialQueue", attr);
    
    //dispatch_set_target_queue
    dispatch_queue_t queue = dispatch_queue_create("concurrentQueue",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.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t firstQueue = dispatch_queue_create("com.starming.gcddemo.firstqueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t secondQueue = dispatch_queue_create("com.starming.gcddemo.secondqueue", 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];
    });
    

    5种队列,主队列(main queue),四种通用调度队列,自己定制的队列。分别是
    QOS_CLASS_USER_INTERACTIVE:user interactive等级表示任务需要被立即执行提供好的体验,用来更新UI,响应事件等。这个等级最好保持小规模。
    QOS_CLASS_USER_INITIATED:user initiated等级表示任务由UI发起异步执行。适用场景是需要及时结果同时又可以继续交互的时候。
    QOS_CLASS_UTILITY:utility等级表示需要长时间运行的任务,伴有用户可见进度指示器。经常会用来做计算,I/O,网络,持续的数据填充等任务。这个任务节能。
    QOS_CLASS_BACKGROUND:background等级表示用户不会察觉的任务,使用它来处理预加载,或者不需要用户交互和对时间不敏感的任务。

    何时使用何种队列类型
    dispatch_once 一般拿来创建单例
    dispatch_async 一个异步API

    //代码框架
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         // 耗时的操作
         dispatch_async(dispatch_get_main_queue(), ^{
              // 更新界面
         });
    });
    
    //下载图片的示例
    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延后执行

    - (void)foo
    {
         double delayInSeconds = 2.0;
         dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t) (delayInSeconds * NSEC_PER_SEC));
         dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
              [self bar];
         });
    }
    

    其中用的宏有多种

     #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);
    

    dispatch_barrier_async

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

    //创建队列
    self.isolationQueue = dispatch_queue_create([label UTF8String], DISPATCH_QUEUE_CONCURRENT);
    //改变setter
    - (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);
              }
         });
    }
    - (void)dispatchBarrierAsyncDemo {
        //防止文件读写冲突,可以创建一个串行队列,操作都在这个队列中进行,没有更新数据读用并行,写用串行。
        dispatch_queue_t dataQueue = dispatch_queue_create("com.starming.gcddemo.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进行快速迭代

    类似for循环,但是在并发队列的情况下dispatch_apply会并发执行block任务

    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.starming.gcddemo.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.starming.gcddemo.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
            });
        }
    }
    

    Block组合Dispatch_groups

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

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

    - (void)dispatchGroupWaitDemo {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.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(@"go on");
    }
    

    使用dispatch_group_notify的例子

    //dispatch_group_notify
    - (void)dispatchGroupNotifyDemo {
        dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.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.starming.gcddemo.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");
    }
    

    如何对现有API使用dispatch_group_t

    //NSURLConnection也可以这样做
    + (void)withGroup:(dispatch_group_t)group
         sendAsynchronousRequest:(NSURLRequest *)request
         queue:(NSOperationQueue *)queue
         completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
    {
         if (group == NULL) {
              [self sendAsynchronousRequest:request
                   queue:queue
                   completionHandler:handler];
         } else {
              dispatch_group_enter(group);
              [self sendAsynchronousRequest:request
                        queue:queue
                        completionHandler:^(NSURLResponse *response, NSData *data, NSError *error){
                   handler(response, data, error);
                   dispatch_group_leave(group);
              }];
         }
    }
    

    ·dispatch_group_async等价于dispatch_group_enter() 和 dispatch_group_leave()的组合。
    ·dispatch_group_enter() 必须运行在 dispatch_group_leave() 之前。
    ·dispatch_group_enter() 和 dispatch_group_leave() 需要成对出现的

    Dispatch Block

    ·创建block

    - (void)createDispatchBlock {
        //normal way
        dispatch_queue_t concurrentQueue = dispatch_queue_create("com.starming.gcddemo.concurrentqueue",DISPATCH_QUEUE_CONCURRENT);
        dispatch_block_t block = dispatch_block_create(0, ^{
            NSLog(@"run block");
        });
        dispatch_async(concurrentQueue, block);
    
        //QOS way
        dispatch_block_t qosBlock = dispatch_block_create_with_qos_class(0, QOS_CLASS_USER_INITIATED, -1, ^{
            NSLog(@"run qos block");
        });
        dispatch_async(concurrentQueue, qosBlock);
    }
    

    ·dispatch_block_wait:可以根据dispatch block 来设置等待时间,参数DISPATCH_TIME_FOREVER会一直等待block结束

    - (void)dispatchBlockWaitDemo {
        dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
        dispatch_block_t block = dispatch_block_create(0, ^{
            NSLog(@"star");
            [NSThread sleepForTimeInterval:5.f];
            NSLog(@"end");
        });
        dispatch_async(serialQueue, block);
        //设置DISPATCH_TIME_FOREVER会一直等到前面任务都完成
        dispatch_block_wait(block, DISPATCH_TIME_FOREVER);
        NSLog(@"ok, now can go on");
    }
    

    ·dispatch_block_notify:可以监视指定dispatch block结束,然后再加入一个block到队列中。三个参数分别为,第一个是需要监视的block,第二个参数是需要提交执行的队列,第三个是待加入到队列中的block

    - (void)dispatchBlockNotifyDemo {
        dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
        dispatch_block_t firstBlock = dispatch_block_create(0, ^{
            NSLog(@"first block start");
            [NSThread sleepForTimeInterval:2.f];
            NSLog(@"first block end");
        });
        dispatch_async(serialQueue, firstBlock);
        dispatch_block_t secondBlock = dispatch_block_create(0, ^{
            NSLog(@"second block run");
        });
        //first block执行完才在serial queue中执行second block
        dispatch_block_notify(firstBlock, serialQueue, secondBlock);
    }
    

    ·dispatch_block_cancel:iOS8后GCD支持对dispatch block的取消

    - (void)dispatchBlockCancelDemo {
        dispatch_queue_t serialQueue = dispatch_queue_create("com.starming.gcddemo.serialqueue", DISPATCH_QUEUE_SERIAL);
        dispatch_block_t firstBlock = dispatch_block_create(0, ^{
            NSLog(@"first block start");
            [NSThread sleepForTimeInterval:2.f];
            NSLog(@"first block end");
        });
        dispatch_block_t secondBlock = dispatch_block_create(0, ^{
            NSLog(@"second block run");
        });
        dispatch_async(serialQueue, firstBlock);
        dispatch_async(serialQueue, secondBlock);
        //取消secondBlock
        dispatch_block_cancel(secondBlock);
    }
    

    使用dispatch block object(调度块)在任务执行前进行取消

    ·dispatch block object可以为队列中的对象设置

    Dispatch IO文件操作

    dispatch io读取文件的方式类似于下面的方式,多个线程去读取文件的切片数据,对于大的数据文件这样会比单线程要快很多。
    ·dispatch_io_create:创建dispatch io
    ·dispatch_io_set_low_water:指定切割文件大小
    ·dispatch_io_read:读取切割的文件然后合并。

    Dispatch Semaphore的介绍

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

    //dispatch semaphore
    - (void)dispatchSemaphoreDemo {
        //创建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);
        NSLog(@"continue");
    }
    

    ·NSRecursiveLock:递归锁,可以在一个线程中反复获取锁不会造成死锁,这个过程会记录获取锁和释放锁的次数来达到何时释放的作用。
    ·NSDistributedLock:分布锁,基于文件方式的锁机制,可以跨进程访问。
    ·NSConditionLock:条件锁,用户定义条件,确保一个线程可以获取满足一定条件的锁。因为线程间竞争会涉及到条件锁检测,系统调用上下切换频繁导致耗时是几个锁里最长的。
    ·OSSpinLock:自旋锁,不进入内核,减少上下文切换,性能最高,但抢占多时会占用较多cpu,这时使用pthread_mutex较好。
    ·pthread_mutex_t:同步锁基于C语言,底层api性能高,使用方法和其它的类似。
    @synchronized:更加简单。

    dispatch_suspend和dispatch_resume挂起和恢复队列

    dispatch_suspend这里挂起不会暂停正在执行的block,只是能够暂停还没执行的block。

    相关文章

      网友评论

          本文标题:GCD解析

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