iOS——GCD

什么是GCD

摘自苹果的官方说明

Grand Central Dispatch(GCD)
是异步执行任务的技术之一。一般将应用程序中记述的线程管理用的代码在系统级实现。开发者只需要定义想执行的任务并追加到适当的Dispatch Queue中,GCD就能生成必要的线程并计划执行任务。由于线程管理是作为系统的一部分来实现的,因此可统一管理,也可执行任务,这样就比以前的线程更有效率。
GCD用非常简洁的记述方法,实现了极为复杂的多线程编程。

多线程编程

一个CPU一次只能执行一个命令,不能执行某处分开的并列的两个命令,所以通过CPU执行的CPU命令列就好比一条无分叉的大道,其执行不会出现分歧。就像下图一样:

在这里插入图片描述

这里所说的“一个CPU执行的CPU命令列为一条无分叉路径”就是线程。同时存在多条这样的路径就称为“多线程”。

但是多线程编程时会出现很多问题,使用GCD大大简化了偏于复杂的多线程编程的源代码,并且可以避免多线程的一些缺点。

GCD的API

Dispatch Queue

dispatch_async (queue, ^{
	//想执行的任务
}

该源代码使用Block语法“定义想执行的任务”,通过dispatch_async函数“追加”赋值在变量queue的“Dispatch Queue”中。仅这样就可使指定的Block在另一线程中执行。

Dispatch Queue是执行处理的等待队列,按照追加任务的顺序(先进先出)执行处理。如下图:
在这里插入图片描述
在执行处理时,存在两种Dispatch Queue,一种是等待现在执行中处理的Serial Dispatch Queue,另一种是不等待现在执行中处理的Concurrent Dispatch Queue。(这个后面有讲到)

dispatch_queue_create
从上面我们知道有两种队列,那么该如何创建呢?我们可以通过GCD的API dispatch_queue_create生成Dispatch Queue

dispatch_queue_t mySerialDispatchQueue = dispatch_queue_create("com.example.gcd.MySerialDispatchQueue", NULL);

参数列表:
第一个参数指定Serial Dispatch Queue的名称,命名推荐使用应用程序ID这种逆序全程域名。
第二个参数:生成Serial Dispatch Queue类时,则指定为NULL,生成Concurrent Dispatch Queue时,指定为DISPATCH_QUEUE_CONCURRENT。
说明:使用dispatch_queue_create函数可以生成任意多个Dispatch Queue,如果过多使用会消耗大量内存。但是使用多个Serial Dispatch Queue可以避免多线程编程引起的多个线程对数据的竞争。
在这里插入图片描述

dispatch_queue_create的返回值为dispatch_queue_t类型。

生成的Dispatch Queue必须由程序员手动释放。

dispatch_release(queue);

Main Dispatch Queue/Global Dispatch Queue

第二种方法是获取系统标准提供的Dispatch Queue。
Main Dispatch Queue/Global Dispatch Queue是不特意生成的系统提供的Dispatch Queue。

dispatch_set_target_queue

dispatch_set_target_queue函数生成的Dispatch Queue不管是Serial Dispatch
Queue还是Concurrent Dispatch Queue都默认和优先级Global Dispatch Queue相同执行优先的线程。

dispatch_after

想在指定时间后执行处理的情况,可以使用dispatch_after。
dispatch_after不是在指定时间后执行处理,而是在指定时间追加处理到Dispatch
Queue。和在指定时间后使用dispatch_async函数追加Block到Main Dispatch Queue的相同。

Dispatch Group

在追加到Dispatch Queue中的多个处理全部结束后想执行结束处理,这种情况经常出现。只使用一个Serial Dispatch
Queue时,只要将想执行的处理全部追加到该Serial Dispatch
Queue中并在最后追加结束处理,即可实现。但是在使用Concurrent Dispatch Queue时或同时使用多个Dispatch
Queue时,情况会很复杂。这种时候使用Dispatch Group。 无论向什么样的Dispatch Queue中追加处理,使用
Dispatch Group都可以监视这些处理执行的结束。

dispatch_barrier_async

使用Serial Dispatch Queue可避免数据竞争的问题。
写入处理确实不可与其他的写入处理以及包含读取处理的其他某些处理并行执行。如果读取处理只和读取处理并行执行,就不会发生问题。
为了高效率的进行访问,读取处理追加到Concurrent Dispatch
Queue中,写入处理在人一个读取处理没有执行的情况下,追加到Serial Dispatch Queue中即可。 利用Dispatch
Group和dispatch_set_target_queue函数也可实现,但会很复杂。可以用dispatch_barrier_async函数同dispatch_queue_create函数生成的Concurrent
Dispatch Queue一起使用。

dispatch_sync

dispatch_async函数的“async”意味着“非同步”,就是将Block“非同步”地追加到指定的Dispatch
Queue中,dispatch_async函数不做任何等待。
dispatch_sync以为着“同步”,也就是将指定的Block“同步”追加到指定的Dispatch
Queue中,在追加Block结束之前,dispatch_sync会一直等待。

dispatch_apply

dispatch_apply是 dispatch_sync函数和Dispatch
Group的关联API。该函数按指定的次数将指定的Block追加到指定的Dispatch Queue中,等待全部处理执行结束。

dispatch_suspend/dispatch_resume

当追加大量的处理到Dispatch Queue时,在追加处理的过程中,有时希望不执行已追加的处理。这种情况下,挂起Dispatch
Queue即可,可以执行时在恢复它。 dispatch_suspend函数挂起指定的Dispatch Queue。
dispatch_resume函数恢复指定的Dispatch Queue。

Dispatch Semaphore

Dispatch Semaphore是持有计数的信号,该计数是多线程编程中的计数类型信号。

dispatch_once

dispatch_once是保证应用程序中只执行一次指定处理的API。(单例模式)

Dispatch I/O

在读取较大文件时,如果将文件分成合适的大小并使用Global Dispatch
Queue并列读取的话,会比一般的读取快很多。实现这一功能的就是 Dispatch I/O和Dispatch Data。
通过Dispatch I/O读写文件时,使用Global Dispatch
Queue将一个文件按某个大小read/write,将文件分为一块一块地进行读取处理。分割的数据通过使用Dispatch
Data可以更为简单的结合和分割。

使用GCD的好处

  • GCD可用于多核的并行运算
  • GCD会自动利用更多的CPU内核(双核、四核)
  • GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
  • 程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码

GCD任务和队列

任务:就是执行操作的意思,也就是要在线程中执行的那段代码。在GCD中就是放在Block中的内容。

执行任务有两种方式:

同步执行(sync)

  • 同步添加任务到指定的队列中,在添加的任务执行结束之前,会一直等待,直到队列中的任务完成之后才能执行
    dispatch_sync,这个函数会把一个block加入到指定的队列中,而且会一直等到执行完blcok,这个函数才返回。因此在block执行完之前,调用dispatch_sync方法的线程是阻塞的。
  • 只能在当前线程中执行任务,不具备开启新线程的能力

异步执行(async)

  • 异步添加任务到指定的队列中,不会做任何的等待,可以继续执行任务
    使用dispatch_async,这个函数也会把一个block加入到指定的队列中,但是和同步执行不同的是,这个函数把block加入队列后不等block的执行就立刻返回了。
  • 在新线程中执行任务,具备开启新线程的能力。

举个例子:我们要给A和B打电话
同步执行:我们给A打电话的时候,不能同时打给B。至于等到和A打完了,才能打给B(等待任务执行结束)。并且只能使用当前这一个电话(不具备开启新线程的能力)
异步执行:我们给A打电话的时候,不用等着和A通话结束,就能同时打给B(不用等待任务执行结束)。而且除了当前电话,我们还可以使用其他电话(具备开启新线程的能力)

这里异步执行只是具备开启新线程的能力,但不一定会开启新线程,是否开启新线程还与队列的类型有关。

队列:这里的队列指任务队列,即用来存放任务的队列。队列是一种特殊的线性表,采用FIFO(先进先出)的原则,即新任务总是被插入到队列的末尾,而读取任务的时候总是从队列的头部开始读取。每读取一个任务,则从队列中释放一个任务。

在GCD中有两种队列:串行队列和并发队列。

  • 串行队列(Serial Dispatch Queue):每次只有一个任务被执行,一个任务执行完再执行下一个,一个接着一个执行。
  • 并发队列(Concurrent Dispatch queue):可以让多个任务并发(同时)执行。并发队列的并发功能只有在异步执行(async)方法下才有效这个队列中的任务也是按着先进先出的方式开始执行,但结束时间不确定

在这里插入图片描述

在这里插入图片描述

GCD的使用步骤

  1. 创建一个队列(串行队列或并发队列)
  2. 将任务追加到任务的等待队列中,然后系统会根据任务类型执行任务(同步执行或异步执行)

队列的创建

可以使用dispatch_queue_create来创建对象,需要传入两个参数。
第一个参数表示队列的唯一标识符,用于DEBUG,可为空;第二个参数用来识别是串行队列还是并发队列。

DISPATCH_QUEUE_SERIAL表示串行队列,
DISPATCH_QUEUE_CONCURRENT表示并发队列。

// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);

任务的创建

// 同步执行任务创建方法
dispatch_sync(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});
// 异步执行任务创建方法
dispatch_async(queue, ^{
    NSLog(@"%@",[NSThread currentThread]);    // 这里放任务代码
});

到这里我们已经知道了串行队列、并发队列两种队列方式,以及两种任务执行方式同步执行、异步执行。
从而有以下四种不同的组合方式
同步执行 + 并发队列
异步执行 + 并发队列
同步执行 + 串行队列
异步执行 + 串行队列

还有全局并发队列和主队列。全局并发队列可以作为普通的队列来使用。但是主队列因为有点特殊,所以又产生了两种组合
同步执行 + 主队列
异步执行 + 主队列

任务与队列不同组合方式的区别
在这里插入图片描述
注意:在主线程中调用 主队列+同步执行 会导致死锁问题,因为主队列中追加的任务 与主线程本身的任务 两者相互等待, 阻塞了主线程, 最终导致主线程死锁
在其他线程中调用 主队列+同步执行, 就不会阻塞主队列,不会造成死锁问题。最终结果就是不会开启新线程,串行执行任务。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值