Objective-C高级编程笔记三(GCD)

示例代码下载

概要

CGD是异步执行任务的技术之一,一般将引用程序中记述的线程管理用的代码在系统级中实现。开发者只需要定义想执行的任务并追加到Dispatch Queue中,GCD就能生成线程并计划执行。由于线程管理是作为系统的一部分实现的,因此可统一管理,也可执行任务,这样比直接使用线程更有效率。

多线程程序可以在某个线程和其他线程之间反复多次进行上下文切换,因此看上去好像一个CPU能够并列的执行多个线程一样。在具有多个CPU核的情况下,是真正地提供了多个CPU核并行执行多个线程的技术。

多线程编程

利用多线程编程的技术成为“多线程编程”。

多线程编程实际上是一种容易发生各种问题的编程技术。如多个线程更新相同的资源会导致数据不一致(资源竞争)、等待时间的线程会导致多个线程相互持续等待(死锁)、太多线程会消耗大量内存等。要回避这些问题有许多方法,但程序都偏于复杂。

尽管极易发生各种问题,也应当使用多线程编程,因为多线程能保证应用程序的响应性能。在主线程中执行长时间的处理,会妨碍主线程中的主循环RunLoop的执行,从而导致不能更新用户界面、应用程序的界面长时间停滞的等问题。

GCD的API

Dispatch Queue

“Dispatch Queue”如其名称所示,是执行处理的等待队列。编程人员通过dispatch_async函数等API,在Block语法中记述想执行的处理并将其添加到Dispatch Queue中。Dispatch Queue按照添加的顺序(FIFO,Fist-In-Fist-Out)执行处理。

在执行处理时存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。

Dispatch Queue 说明
Serial Dispatch Queue 等待现在执行中处理结果
Concurrent Dispatch Queue 不等待现在执行中处理结果

意思就是说Serial Dispatch Queue等待正在执行的任务结束后才开始执行下一个任务;而Concurrent Dispatch Queue不管正在执行的任务是否结束都开始执行下一个任务。

Serial Dispatch Queue示例:

dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
for (int index = 0; index < 100; index++) {
    dispatch_async(serialQueue, ^{
        printf("Serial Queue: %i\n", index);
    });
}

按顺序依次打印:

Serial Queue: 0
Serial Queue: 1
Serial Queue: 2
Serial Queue: 3
Serial Queue: 4
...

Concurrent Dispatch Queue示例:

dispatch_queue_t concurrentQueue = dispatch_queue_create("ConcurrentQueue", DISPATCH_QUEUE_CONCURRENT);
for (int index = 0; index < 100; index++) {
    dispatch_async(concurrentQueue, ^{
        printf("Concurrent Queue: %i\n", index);
    });
}

不按顺序打印:

Concurrent Queue: 0
Concurrent Queue: 2
Concurrent Queue: 1
Concurrent Queue: 3
Concurrent Queue: 4
...

Concurrent Dispatch Queue不用等待处理结束,可以并行执行多个处理,所谓并行处理就是使用多个线程同时执行多个处理。但并行的处理数量取决于当前系统的状态,就是Dispatch Queue中的任务处理数、CPU核数以及CPU负荷等当前系统状态来决定Concurrent Dispatch Queue中并行执行的处理数。

系统对Serial Dispatch Queue只生成并使用一个线程。如果生成多个Serial Dispatch Queue就生成多个线程。所以生成数量应当是所必须的数量,不能冲动之下生成大量的Serial Dispatch Queue。对于Concurrent Dispatch Queue来说,不管生成多少,内核只使用有效管理的线程。

多线程更新相同资源导致数据竞争时使用Serial Dispatch Queue。

并行执行不发生数据竞争问题时使用Concurrent Dispatch Queue。

dispatcch_queue_create

生成Dispatch Queue。

Main Dispatch Queue/Global Dispatch Queue

Main Dispatch Queue是主线程执行的Dispatch Queue。因为主线程只有一个,所以Main Dispatch Queue是Serial Dispatch Queue。

Global Dispatch Queue是所有应用程序都能使用的Concurrent Dispatch Queue。没有必要通过dispatcch_queue_create逐个生成Concurrent Dispatch Queue,只要获取Global Dispatch Queue使用即可。

系统提供的Dispatch Queue总结表:

名称 Dispatch Queue 种类 说明
Main Dispatch Queue Serial Dispatch Queue 主线程执行
Global Dispatch Queue(High Priority) Concurrent Dispatch Queue 执行优先级:高(最高优先)
Global Dispatch Queue(Default Priority) Concurrent Dispatch Queue 执行优先级:默认
Global Dispatch Queue(Low Priority) Concurrent Dispatch Queue 执行优先级:低
Global Dispatch Queue(Background Priority) Concurrent Dispatch Queue 执行优先级:后台(最低优先级-可有可无)

dispatch_set_target_queue

dispatch_queue_create函数生成的Dispatch Queue都使用与默认优先级Global Dispatch Queue相同执行优先级的线程。变更Dispatch Queue的执行优先级要使用dispatch_set_target_queue函数。

dispatch_set_target_queue函数的第一个参数为要变更执行优先级的Dispatch Queue,第二个参数是指定要与相同优先级的Global Dispatch Queue。第一个参数如果设置为Main Dispatch Queue/Global Dispatch Queue,则不知道会出现什么情况,因此均不可设置。

如果将多个Serial Dispatch Queue使用dispatch_set_target_queue函数指定目标为某一个Serial Dispatch Queue,那么原先并行执行的多个Serial Dispatch Queue,在目标Serial Dispatch Queue上只能执行一个处理。所以使用这种方式可以防止多个Serial Dispatch Queue之间的并行执行。

多个Serial Dispatch Queue并行执行:

for (int index = 0; index < 10; index++) {
    dispatch_queue_t serialQueue1 = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
    dispatch_async(serialQueue1, ^{
        printf("Serial Queue1: %i\n", index);
    });
}

结果打印:

Serial Queue1: 0
Serial Queue1: 4
Serial Queue1: 1
Serial Queue1: 5
Serial Queue1: 2
Serial Queue1: 7
Serial Queue1: 3
Serial Queue1: 8
Serial Queue1: 6
Serial Queue1: 9

使用dispatch_set_target_queue函数防止多个Serial Dispatch Queue并行执行:

dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
for (int index = 0; index < 10; index++) {
    dispatch_queue_t serialQueue1 = dispatch_queue_create("SerialQueue1", DISPATCH_QUEUE_SERIAL);
    dispatch_set_target_queue(serialQueue1, serialQueue);
    dispatch_async(serialQueue1, ^{
        printf("Serial Queue1: %i\n", index);
    });
}

打印结果:

Serial Queue1: 0
Serial Queue1: 1
Serial Queue1: 2
Serial Queue1: 3
Serial Queue1: 4
Serial Queue1: 5
Serial Queue1: 6
Serial Queue1: 7
Serial Queue1: 8
Serial Queue1: 9

dispatch_after

dispatch_after在知道的时间后将知道的Block追加到指定的Dispatch Queue上。

并不是在指定时间后执行处理,而是追加处理到指定的Dispatch Queue。

Dispatch Group

Dispatch Group用于监视一批处理的结束,一旦检测到所有处理结束,就会将结果处理追加到Dispatch Queue中。

dispatch_group_create函数生成dispatch_group_t类型的Dispatch Group。

dispatch_group_async与dispatch_async函数相同,追加Block到指定的Dispatch Queue中。

dispatch_group_wait函数在指定的时间内等待所有处理执行结束。返回值为0说明在指定时间内全部处理执行结束;返回值不为0说明过了指定时间Dispatch Queue还有处理在执行。

dispatch_group_notify函数在Dispatch Group中追加结束处理到指定的Dispatch Queue。

dispatch_queue_t serialQueue = dispatch_queue_create("SerialQueue", DISPATCH_QUEUE_SERIAL);
dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, serialQueue, ^{
    printf("Serial Queue\n");
});
dispatch_group_async(group, globalQueue, ^{
    printf("Global Queue\n");
});
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    printf("Main Queue\n");
});
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC))) == 0) {
    printf("Dispatch Group结束\n");
} else {
    printf("Dispatch Group在1秒后没有结束\n");
}

运行结果如下:

Global Queue
Serial Queue
Dispatch Group结束
Main Queue

dispatch_barrier_async

dispatch_barrier_async函数会将指定处理等待前面追加到Concurrent Dispatch Queue中的所有处理全部结束之后再追加到该Concurrent Dispatch Queue中;但dispatch_barrier_async函数却不会等待而是立即返回。然后dispatch_barrier_async函数追加的处理结束后,才恢复Concurrent Dispatch Queue处理后面追加的处理。

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.xxxx.xxxx.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrentQueue, ^{
    printf("Concurrent Queue: 1\n");
});
dispatch_async(concurrentQueue, ^{
    printf("Concurrent Queue: 2\n");
});
dispatch_barrier_async(concurrentQueue, ^{
    printf("Concurrent Queue: 3\n");
});
dispatch_async(concurrentQueue, ^{
    printf("Concurrent Queue: 4\n");
});
dispatch_async(concurrentQueue, ^{
    printf("Concurrent Queue: 5\n");
});

运行结果1、2永远在3前面,4、5永远在3后面:

Concurrent Queue: 1
Concurrent Queue: 2
Concurrent Queue: 3
Concurrent Queue: 5
Concurrent Queue: 4

使用Concurrent Dispatch Queue和dispatch_barrier_async函数可以实现高效率的数据库访问和文件访问。

dispatch_sync/dispatch_async

dispatch_sync函数同步地将指定的Block处理追加到指定的Dispatch Queue中。dispatch_sync函数会一直等待直到Block处理完成才返回,意味着当前线程停止。

dispatch_async函数异步地将指定的Block处理追加到指定的Dispatch Queue中。dispatch_async函数不做任何等待立即返回。

dispatch_apply

dispatch_apply函数按指定的次数将Block追加到指定的Dispatch Queue中,并等待全部处理结束。

dispatch_queue_t concurrentQueue = dispatch_queue_create("com.xxxx.xxxx.concurrentQueue", DISPATCH_QUEUE_CONCURRENT);
dispatch_apply(5, concurrentQueue, ^(size_t index) {
    printf("Concurrent Queue: %li\n", index);
});

printf("Concurrent Queue End.\n");

"Concurrent Queue End."永远打印在最后如下所示:

Concurrent Queue: 0
Concurrent Queue: 1
Concurrent Queue: 2
Concurrent Queue: 4
Concurrent Queue: 3
Concurrent Queue End.

dispatch_apply函数与dispatch_sync函数相同会等待处理执行结束。因此推荐在dispatch_async函数函数中非同步的执行dispatch_apply函数。

dispatch_suspend/dispatch_resume

dispatch_suspend函数挂起指定的Dispatch Queue。

dispatch_resume函数恢复指定的Dispatch Queue。

这些函数对已经执行的处理没有影响,挂起后追加到Dispatch Queue但尚未执行的处理将停止,恢复后使得那些没有执行的处理能够继续执行。

Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。是用更细粒度的对象实现排他控制。

dispatch_semaphore_create函数创建Dispatch Semaphore并用参数初始化计数值。

dispatch_semaphore_wait函数在指定的时间内等待Dispatch Semaphore的计数值达到大于或等于1,并对计数减1返回。

dispatch_semaphore_signal函数将Dispatch Semaphore的计数加1。

dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_semaphore_t semaphore = dispatch_semaphore_create(1);
NSMutableArray *mArray = [NSMutableArray arrayWithCapacity:1];
for (int index = 0; index < 10000; index++) {
    dispatch_async(globalQueue, ^{
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        [mArray addObject:@(index)];
        dispatch_semaphore_signal(semaphore);
    });
}

dispatch_once

dispatch_once函数保证在应用程序执行期间指定的Block处理只会执行一次。

Dispatch I/O

dispatch_io_create函数生成Dispatch I/O,并指定发生错误时用来执行处理的Block,以及执行该处理的Dispatch Queue。

dispatch_io_set_low_water函数设定一次读取的大小(分割大小)。

dispatch_io_read函数使用Dispatch Global Queue开始并列读取。当每个分割的文件块读取结束时,将含有文件块数据的Dispatch Data传递给dispatch_io_read函数指定的读取结束时回调用的Block。回调用的Block分析传递过来的Dispatch Data并进行合并处理。

死锁

GCD中的一些函数在指定的Block处理没有结束之前,不会返回。如dispatch_sync、dispatch_apply等。在使用这些同步等待处理执行的函数时,稍有不慎就会导致死锁。如下代码所示:

NSLog(@"1");
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"2");
});
NSLog(@"3");

上面的代码发生死锁,打印结果只有1。在Dispatch Main Queue(主线程)中执行指定的Block处理并等待其执行结束,而此时的主线程正在执行这些代码没法执行追加到Dispatch Main Queue中的Block。从而一直等待下去,线程表现为卡死状态就是所谓的死锁。

你可能感兴趣的:(Objective-C高级编程笔记三(GCD))