GCD相关总结

作者: Jon1993 | 来源:发表于2018-05-31 20:11 被阅读52次

    关键词:异步执行任务的技术、将应用程序的线程管理用的代码在系统级中实现、高效率。

    旧的 API 实现
    - (void)demoPerformSelector{
        [self performSelectorInBackground:@selector(doWork) withObject:nil];
    }
    
    - (void)doWork{
        NSLog(@"doWork........");
        [self performSelectorOnMainThread:@selector(doneWork) withObject:nil waitUntilDone:NO];
    }
    
    - (void)doneWork{
        NSLog(@"doneWork!");
    }
    
    使用 GCD 实现
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"doWork........");
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"doneWork!");
            });
        });
    
    

    1.Dispatch Queue 调度队列

    Dispatch Queue 先进先出的属性(FIFO) 执行处理。有两种 Dispatch Queue,一种是等待现在执行中处理的 Serial Dispatch Queue(串行调度队列),另一种是不等待现在执行中处理的 Concurrent Dispatch Queue(并行调度队列)。
    通过以下源码,比较这两种 Dispatch Queue:

    dispatch_async(queue,block0);
    dispatch_async(queue,block1);
    dispatch_async(queue,block2);
    dispatch_async(queue,block3);
    dispatch_async(queue,block4);
    dispatch_async(queue,block5);
    

    当 queue 为 Serial Dispatch Queue 时,因为要等到现在执行中处理结束,所以首先执行 block0,block0执行结束后,执行 block1,如此重复,同时执行的处理数只能有1个。即执行该源代码后,一定按照以下顺序进行处理。(block0,block1,block2,block3,block4,block5)。
    当 queue 为 Concurrent Dispatch Queue 时,因为不用等待现在执行中的处理结束,所以首先执行 block0,不管 block0 的执行是否结束,都开始执行后面的 block1,不管 block1执行是否结束了,都开始执行后面的 block2,如此重复循环。
    这样虽然不用等待处理结束,可以并行执行多个处理,但并行执行处理的数量取决于当前系统的状态。即 iOS 或 OS X 基于 Dispatch Queue 中的处理数、CPU核数以及 CPU 负荷等当前系统的状态来决定 Concurrent Dispatch Queue 中并行执行的处理数。所谓的“并行执行”,就是使用多个线程同时执行多个处理。

    2.获取 Dispatch Queue

    使用 dispatch_queue_create 创建 Dispatch Queue

    //创建 Serial Dispatch Queue
    dispatch_queue_t mySerialDicpatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);
    //创建 Concurrent Dispatch Queue
    dispatch_queue_t myConcurrentQueue = dispatch_queue_create("com.exmaple.gcd.MuConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
    

    需要注意的是 dispatch_queue_create函数可以创建任意多个 Dispatch Queue,当生成多个 Serial Dispatch Queue 时,虽然一个 Seria Dispatch Queue 中同时只能执行一个追加处理,但如果将处理分别追加到多个 Serial Dispatch Queue 中,各个 Serial Dispatch Queue 会分别执行,即同时执行多个处理。一旦生成 Serial Dispatch Queue 并追加处理,系统对于一个 Serial Dispatch Queue 就只生成并使用一个线程。如果生成 N 个 Serial Dispatch Queue,那么就生成 N 个线程。

    Main Dispatch Queue 和 Global Dispatch Queue

    Main Dispatch Queue,主线程队列,是一个 Serial Dispatch Queue

    dispatch_queue_t mainQueue = dispatch_get_main_queue();
    

    Global Dispatch Queue,全局队列,是 Concurrent Dispatch Queue,有四种优先级High PriorityDefault PriorityLow PriorityBackground Priority

    dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    

    3. dispatch_set_target_queue()设置参考队列

    dispatch_set_target_queue(dispatch_object_t object,
            dispatch_queue_t _Nullable queue);
    

    该函数有两种用法,第一种是设置 Dispatch Queue 的优先级

    第一个参数填需要更改优先级的 Dispatch Queue,第二个参数填要与要指定的优先级相同优先级的 Global Dispatch Queue。

    dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.gcd.mySerialDispatchQueue", DISPATCH_QUEUE_SERIAL);
    dispatch_queue_t globalQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
    
    dispatch_set_target_queue(mySerialDispatchQueue, globalQueueLowPriority);
    

    第二种用法可以用来线程同步
    当我们想让不同队列中的任务同步的执行时,我们可以创建一个串行队列,然后将这些队列的 target 指向新创建的队列

        dispatch_queue_t serailQueue1 = dispatch_queue_create("com.gcd.serialQueue1", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t serailQueue2 = dispatch_queue_create("com.gcd.serialQueue2", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t concurrentQueue1 = dispatch_queue_create("com.gcd.concurrentQueue1", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t concurrentQueue2 = dispatch_queue_create("com.gcd.concurrentQueue2", DISPATCH_QUEUE_CONCURRENT);
        
        //创建目标队列(参考队列)
        dispatch_queue_t targetQueue = dispatch_queue_create("com.gcd.targetQueue", DISPATCH_QUEUE_SERIAL);
        //设置参考
        dispatch_set_target_queue(serailQueue1, targetQueue);
        dispatch_set_target_queue(serailQueue2, targetQueue);
        dispatch_set_target_queue(concurrentQueue1, targetQueue);
        dispatch_set_target_queue(concurrentQueue2, targetQueue);
    
        NSLog(@"******start******");
        dispatch_async(serailQueue1, ^{
            NSLog(@"current Thread:%@ task1",[NSThread currentThread]);
            sleep(3);
            NSLog(@"task1 end");
        });
        dispatch_async(serailQueue2, ^{
            NSLog(@"current Thread:%@ task2",[NSThread currentThread]);
            sleep(2);
            NSLog(@"task2 end");
        });
        dispatch_async(concurrentQueue1, ^{
            NSLog(@"current Thread:%@ task3",[NSThread currentThread]);
            sleep(1);
            NSLog(@"task3 end");
        });
        dispatch_async(concurrentQueue2, ^{
            NSLog(@"current Thread:%@ task4",[NSThread currentThread]);
            NSLog(@"task4 end");
        });
        NSLog(@"******end******");
    
    

    输出结果:

    ******start******
    ******end******
    current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task1 
    task1 end
    current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task2
    task2 end
    current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task3
    task3 end
    current Thread:<NSThread: 0x600000468580>{number = 5, name = (null)} task4
    task4 end
    
    

    通过dispatch_set_target_queue()函数以及参考队列targetQueue,使得串行队列serailQueue1,serailQueue2与并行队列concurrentQueue1,concurrentQueue2同步。

    4. dispatch_after()延时执行(准确的说是追加任务)

    dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 3ull * NSEC_PER_SEC);
    dispatch_after(time, dispatch_get_main_queue(), ^{
        NSLog(@"**********************");
    });
    

    注意:
    dispatch_after()函数并不是在指定时间后执行任务,而是在指定时间追加任务到 Dispatch Queue 中。
    示例中与3秒后用 dispatch_async()函数追加 block 中的任务到 Main Dispatch Queue 相同。
    Main Dispatch Queue 在主线程的 RunLoop 中执行,假设每隔 1/60秒执行一次的 RunLoop,block 最快在3s 后执行,最慢在 3+1/60 秒后执行,并且在 Main Dispatch Queue 中有大量追加的任务或者主线程本身处理有延迟时,时间会更长。
    dispatch_time_t 表示的是一个时刻,可以由 dispatch_time()函数或者 dispatch_walltime()函数获得
    dispatch_time()函数 能够获取从第一个参数指定的时间开始,到第二个参数指定的纳秒(毫微秒)后的时间 常用于计算相对时间
    dispatch_walltime()函数由 POSIX 中使用的 struct timespec 类型的时间得到 dispatch_time_t 类型的值,常用计算绝对时间

     //ull 数值字面量(unsigned long long) DISPATCH_TIME_NOW 表示现在的时间
     //获取从现在开始1s 后的时间
     dispatch_time_t time1 = dispatch_time(DISPATCH_TIME_NOW, 1ull * NSEC_PER_SEC);
     //获取从现在开始100毫秒后的时间
     dispatch_time_t time2 = dispatch_time(DISPATCH_TIME_NOW, 150ull * NSEC_PER_MSEC);
    
        // 通过 NSDate 对象获取 dispatch_time_t 类型值
        dispatch_time_t getDispatchTimeByDate(NSDate *date){
        NSTimeInterval interval = [date timeIntervalSince1970];
        double second;
        double subsecond = modf(interval, &second);
        struct timespec time;
        time.tv_sec = second;
        time.tv_nsec = subsecond * NSEC_PER_SEC;
        dispatch_time_t milestone = dispatch_walltime(&time, 0);
        return milestone;
    }
    
    

    5. Dispatch Group

    Dispatch Group可以用于在追加到 Dispatch Queue 中的多个任务全部结束后想执行的结束任务的操作。

    • dispatch_group_create()创建 Dispatch Group
    • dispatch_group_async()追加任务到指定的 Dispatch Queue 中,且指定任务属于指定的 Dispatch Group
    • dispatch_group_notify()所有任务执行完毕后再追加执行的任务
    • dispatch_group_wati() 在指定的等待时间前(超时时间),等待 group 中全部任务处理结束,会卡住当前线程
        dispatch_queue_t serialQueue = dispatch_queue_create("com.gcd.serialQueue", DISPATCH_QUEUE_SERIAL);
        dispatch_queue_t conCurrentQueue = dispatch_queue_create("com.gcd.conCurrentQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_group_t group = dispatch_group_create();
        
        NSLog(@"******start******");
        dispatch_group_async(group, serialQueue, ^{
            NSLog(@"current Thread:%@ task1",[NSThread currentThread]);
            sleep(3);
            NSLog(@"task1 end");
        });
        dispatch_group_async(group, conCurrentQueue, ^{
            NSLog(@"current Thread:%@ task2",[NSThread currentThread]);
            sleep(2);
            NSLog(@"task2 end");
        });
        dispatch_group_async(group, serialQueue, ^{
            NSLog(@"current Thread:%@ task3",[NSThread currentThread]);
            sleep(1);
            NSLog(@"task3 end");
        });
        dispatch_group_async(group, conCurrentQueue, ^{
            NSLog(@"current Thread:%@ task4",[NSThread currentThread]);
            NSLog(@"task4 end");
        });
        
        //第二个参数填超时时间 DISPATCH_TIME_FOREVER 表示永远等待
    //    long result = dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        // 设置2秒的超时时间
        dispatch_time_t time = dispatch_time(DISPATCH_TIME_NOW, 2ull * NSEC_PER_SEC);
        long result = dispatch_group_wait(group, time);
        if (result == 0) {
            NSLog(@"Group 中所有任务执行完毕");
        }else{
            
            NSLog(@"Group 中任有任务执行中");
        }
        dispatch_group_notify(group, dispatch_get_main_queue(), ^{
            NSLog(@"current Thread:%@ All END",[NSThread currentThread]);
        });
        NSLog(@"******end******");
    

    6. dispatch_barrier_async

    dispatch_barrier_async函数可以理解为一种分割任务的栅栏,通过dispatch_barrier_async追加的任务同时只执行一个

        dispatch_async(conCurrentQueue, read_block1);
        dispatch_async(conCurrentQueue, read_block2);
        dispatch_async(conCurrentQueue, read_block3);
        
        dispatch_barrier_async(conCurrentQueue, write_block4);
    
        dispatch_async(conCurrentQueue, read_block5);
        dispatch_async(conCurrentQueue, read_block6);
        dispatch_async(conCurrentQueue, read_block7);
    

    示例中 block1,block2,block3 并行执行,都执行完毕后会执行 write_block4,然后block5,block6,block7再并行执行。
    使用 Concurrent Dispatch Queue 配合 dispatch_barrier_async 函数可以实现高效的数据库访问和文件访问。

    7. dispatch_sync

    dispatch_sync同步追加任务到队列中,不能开辟线程,且只有在追加的任务完成后才返回

    dispatch_sync 函数引起死锁问题

    产生死锁的条件是在串行队列所在的线程中,使用 dispatch_sync 函数追加任务到该串行队列中。

    示例一

     //在主线程中调用以下代码会产生死锁
     dispatch_sync(dispatch_get_main_queue(), ^{
                NSLog(@"死锁~~");
                NSLog(@"current thread:%@",[NSThread currentThread]);
            });
    

    示例二

     dispatch_queue_t serialDispatchQueue = dispatch_queue_create("com.gcd.serialDispatchQueue", NULL);
        dispatch_async(serialDispatchQueue, ^{
            NSLog(@"current thread:%@",[NSThread currentThread]);
            dispatch_sync(serialDispatchQueue, ^{
                NSLog(@"死锁");
            });
        });
    

    示例一:由于主线程所在的队列 Main Dispatch Queue 为一个串行队列,所以在主线程中使用dispatch_sync函数同步追加任务到 Main Dispatch Queue 中会产生死锁。
    示例二:创建了串行队列 serialDispatchQueue,使用dispatch_async异步追加任务到该队列,此时该队列中的任务都是在该队列的线程上执行,此时使用dispatch_sync函数再同步追加任务到该队列中,由于是在串行队列所在的线程中同步追加任务,所以产生了死锁。

    dispatch_sync 函数引起死锁的原因

    • 调用dispatch_sync函数会立即阻塞调用时该函数所在的线程,等待dispatch_sync函数返回
    • 由于追加任务的队列为串行队列所以,采用 FIFO 的顺序执行任务,很显然我们追加的任务位于队列后面,现在不会立即执行
    • 如果任务不执行完,dispatch_sync函数不会返回,所以线程会一直被阻塞

    8. dispatch_apply

    dispatch_apply函数会按指定的次数将任务 block 追加到指定的 Dispatch Queue 中,并等待全部处理执行结束。

       dispatch_queue_t concurrentDispatchQueue = dispatch_queue_create("com.gcd.concurrentDispatchQueue", DISPATCH_QUEUE_CONCURRENT);
       dispatch_apply(5, concurrentDispatchQueue, ^(size_t index) {
            NSLog(@"%zu current thread:%@",index,[NSThread currentThread]);
        });
        NSLog(@"******end******");
    

    执行结果:0,2,1,3,4,5 end
    因为是在 Concurrent Dispatch Queue 中执行任务的,所以几个任务是并行执行。

    注意: dispatch_apply函数会阻塞当前线程,等待任务全部执行完毕再返回,所以在主线程中调用追加任务到 Main Dispatch Queue 会造成死锁。

    9. dispatch_suspenddispatch_resume函数

    dispatch_suspend函数用于挂起指定的 Dispatch Queue
    dispatch_resume函数用于恢复指定的 Dispatch Queue
    这些函数对已经开始执行的任务没有影响,挂起后,追加到 Dispatch Queue 中,但尚未执行的任务在此之后会暂停执行(任务仍然可以继续追加,但新追加的也会暂停执行),而恢复则使得这些任务继续执行。

    10. Dispatch Semaphore 信号量

    Dispatch Semaphore 信号量在 GCD 常被用于进行同步和控制并发。
    信号量是一个整形值并且具有一个初始计数值,并且支持两个操作:信号通知和等待。当一个信号量被信号通知,其计数会被加1。当在一个线程上设置一个信号量等待时,线程会被阻塞至超时时间(如果有必要的话可以设置为一直阻塞),只有当计数器大于零,计数才会被减1并且该线程恢复。
    信号量可以被用来作为线程锁,也可以用来控制并发线程数。

           //如果设置为10的话,并发线程最多为10个
    //    dispatch_semaphore_t sema = dispatch_semaphore_create(10);
        //如果设置为1的话,并发线程为1个,可以保证数据安全
        dispatch_semaphore_t sema = dispatch_semaphore_create(1);
        dispatch_queue_t gloabQueue = dispatch_get_global_queue(0, 0);
        NSMutableArray *array = [NSMutableArray arrayWithCapacity:100];
        for (int i = 0; i < 100; i++) {
            dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
            dispatch_async(gloabQueue, ^{
                NSLog(@"thread%@ add %d",[NSThread currentThread] ,i);
                [array addObject:@(i)];
                dispatch_semaphore_signal(sema);
            });
        }
    

    11. dispatch_once

    dispatch_once函数是保证在应用程序执行中执行一次指定处理的 API。
    使用dispatch_once函数生成单利,即使在多线程情况下执行,也可保证百分百安全。

        static dispatch_once_t pred;
        dispatch_once(&pred, ^{
            NSLog(@"只会执行一次");
        });
    

    相关文章

      网友评论

      本文标题:GCD相关总结

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