GCD相关总结

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

旧的 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:{number = 5, name = (null)} task1 
task1 end
current Thread:{number = 5, name = (null)} task2
task2 end
current Thread:{number = 5, name = (null)} task3
task3 end
current Thread:{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相关总结)