博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS并发编程(GCD)学习笔记
阅读量:6228 次
发布时间:2019-06-21

本文共 6566 字,大约阅读时间需要 21 分钟。

  hot3.png

NSOperation
NSBlockOperation 
使用 
addExecutionBlock: 
可以添加更多 
block 
到这个 
block operation 
对象

 

多数开发者从来都不需要实现并发 
operation 
对象,我们只需要将 
operations 
添加到 
operation queue。当你提交非并发 operation 
到 
operation queue 
时,queue 会创建线程来运行你的操作,因此也能达到 异步执行的目的。只有你不希望使用 
operation queue 
来执行 
operation 
时,才需要定义并发 
operations 以支持
手工执行 
operations
配置依赖
使用 
NSOperation 
的addDependency: 方法在两个operation对象之 间建立依赖关系。表示当前 
operation 
对象将依赖于参数指定的目标 
operation 
对象。依赖关系不局限于相同 
queue 
中的 
operations 
对象, 
Operation 
对象会管理自己的依赖,因此完全可以在不同的 
queue 
之间 的 
Operation 
对象创建依赖关系。
配置依赖必须在运行 
operation 
和添加 
operation 
到 
queue 
之前进行, 之后添加的依赖关系可能不起作用。
修改Operation的执行优先级
执行顺序首先由已入队列的 
operations 
是否准备好,然后再根据所有
operations 
的相对优先级确定。
是否准备好由对象的依赖关系确定。
通过 
setQueuePriority: 
方法来提升或降低 
operation 
对象的优先级
优先级只能应用于相同 
queue 
中的 
operations
修改底层线程的优先级
设置 
operation 
的线程优先级,你必须在将 
operation 
添加到 
queue 
之前,调用setThreadPriority: 方法进行设置
指定线程优先级为 
0.0 
到 
1.0 
之间的某个数值,0.0 表示最低优先级,1.0 表示最高优先级。默认线程优先级为 
0.5
新的线程优先级只在 
operation 
的 
main 
方法范围内有效。其它所有代 码仍然(包括 
completion block)运行在默认线程优先级
如果你创建了并发 
operation(手工start的operation),并覆盖了 start 
方法,你必须自己配置 线程优先级。
设置completion block
调用 
NSOperation 
的 
setCompletionBlock: 
方法来设置一个 
completion block,你传递的 block 
应该没有参数和返回值。

 Grand Central Dispatch(GCD):系统管理线程,你不需要编写线 程代码。只需定义想要执行的任务,然后添加到适当的 dispatch queue。GCD 会负责创建线程和调度你的任务。系统直接提供线 程管理,比应用实现更加高效。

 
Operation Queue:Objective-C 
对象,类似于 
dispatch queue。你 
定义想要执行的任务,并添加任务到 
operation queue,后者负责 
调度和执行这些任务。和 
GCD 
一样,Operation 
Queue 
也管理了 线程,更加高效。
所有 
operation objects 
都支持以下关键特性:
  •  支持建立基于图的operation objects依赖。可以阻止某个operation 运行,直到它依赖的所有 operation 都已经完成。

  •  支持可选的 completion block,在 operation 的主任务完成后调用。

  •  支持应用使用 KVO 通知来监控 operation 的执行状态。 

  •  支持 operation 优先级,从而影响相对的执行顺序

  •  
    支持取消,允许你中止正在执行的任务
     
当一个 
operation 
对象依赖的所有其它对象都已经执行完成,该 
operation 
就变成准备执行状态(如果你自定义了 
isReady 
方法,则由你的方法确定是否准备好运行)。
对于添加到 
queue 
的 
Operations,执行顺序首先由已入队列的 
operations 
是否准备好,然后再根据所有 
operations 
的相对优先级确定。
是否准备好由对象的依赖关系确定,优先级等级则是 
operation 
对象本
身的一个属性。
dispatch queue对应并发和异步处理
dispatch source对应系统IO事件通知处理

dispatch queue 是类似于对象的结构体,管理你提交给它的任务,而且都是 先进先出的数据结构。因此 queue 中的任务总是以添加的顺序开始执行。GCD 提供了几种 dispatch queues,不过你也可以自己创建。

类型
描述

串行

也称为 
private dispatch queue,每次只执行一个任务,按任务 添加顺序执行。当前正在执行的任务在独立的线程中运行(不同任务的线程可能不同),dispatch queue 管理了这些线程。 通常串行 queue 主要用于对特定资源的同步访问。 你可以创建任意数量的串行 queues,虽然每个 queue 本身每次只能执行一个任务,但是各个 queue 之间是并发执行的。

并发

也称为 global dispatch queue,可以并发执行一个或多个任务 但是任务仍然是以添加到 queue 的顺序启动。每个任务运行 于独立的线程中,dispatch queue 管理所有线程。同时运行的任务数量随时都会变化,而且依赖于系统条件。 你不能创建并发 dispatch queues。相反应用只能使用三个已 经定义好的全局并发 queues:DISPATCH_QUEUE_PRIORITY_DEFAULT、LOW、HIGH
Main dispatch
全局可用的串行 queue,在应用主线程中执行任务。这个 queue 与应用的 run loop 交叉执行。由于它运行在应用的主线程,main queue 通常用于应用的关键同步点。 虽然你不需要创建 main dispatch queue,但你必须确保应用 适当地回收
dispatch_get_main_queue()
使用dispatch_get_current_queue函数用来调试或者测试获得当前队列的标识。
使用函数dispatch_get_main_queue可以得到与应用程序主线程相连的串行调度队列。
concurrent queues和main queue 都是由系统生成而且 dispatch_suspend, dispatch_resume, dispatch_set_context,这些函数对他们无效
dispatch_queue_create 
函数创建串行 
queue,两个参数分别是 queue 
名和一 组 
queue 
属性。调试器和性能工具会显示 
queue 
的名字,便于你跟踪任务的执行。
dispatch_sync会阻塞当前线程
dispatch queues 
的几个关键点:
  •  dispatch queues 相对其它 dispatch queues 并发地执行任务,串行化任务只 能在同一个 dispatch queue 中实现。

  •  系统决定了同时能够执行的任务数量,应用在 100 个不同的 queues 中启动 100 个任务,并不表示 100 个任务全部都在并发地执行(除非系统拥有 100 或更多个核)

  •  系统在选择执行哪个任务时,会考虑 queue 的优先级。

  •  queue中的任务必须在任何时候都准备好运行,注意这点和Operation对

    象不同。
     
    private dispatch queue 
    是引用计数的对象。你的代码中需要 
    retain 
    这些
    queue,另外 dispatch source 
    也可能添加到一个 
    queue,从而增加 retain 
    的计数。因此你必须确保所有 
    dispatch source 
    都被取消,而且适当地调用 
    release。
    (虽然 
    dispatch queue 
    是引用计数的对象,但你不需要 
    retain 
    和 
    release 
    全局并 发 
    queue。因为这些 queue 
    对应用是全局的,retain 和 
    release 
    调用会被忽略。
    你也不需要存储这三个 
    queue 
    的引用,每次都直接调 用 
    dispatch_get_global_queue 
    获得 
    queue 
    就行了.)
    你不需要 
    retain 
    或 
    release 
    全局 
    dispatch queue,包括全局并发 dispatch queue 
    和 
    main dispatch queue。
循环迭代:
如果每次迭代执行的任务与其它迭代独立无关,而且循环迭代执行顺序也无关紧要的话,可以调用dispatch_apply 或 
dispatch_apply_f 
函数来替换循环。 这两个函数为每次循环迭代将指定的 
block 
或函数提交到 
queue。当 dispatch 
到并发 
queue 
时,就有可能同时执行多个循环迭代。
和普通 
for 
循环一样,dispatch_apply 和 
dispatch_apply_f 
函数也是在所有迭代完成之后才会返回。
这两个函数还会阻塞当前线程
  1. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);  
  2. dispatch_apply(count, queue, ^(size_t i) {  
  3. printf("%un",i);  
  4. });
暂停:
使用dispatch_suspend 函 数挂起一个 
dispatch queue;使用 dispatch_resume 
函数继续 
dispatch queue。调 
用 
dispatch_suspend 
会增加 
queue 
的引用计数,调用 
dispatch_resume 
则减少
queue 的引用计数。当引用计数大于 时,queue 就保持挂起状态。因此你必须 对应地调用 suspend 和 resume 函数。
挂起和继续是异步的,而且只在执行 
block 
之间生效。挂起一个 
queue 
不会 导致正在执行的 
block 
停止。
为Queue提供一个清理函数
使用 
dispatch_set_finalizer_f 
函数为 
queue 
指 定一个清理函数,当 
queue 
的引用计数到达 
时,就会执行该清理函数
设置completion block
没有固定模式,需自己在工作block中实现回调block,一般情况下,是在完成工作block时,
提交指定的 
Block 
或函数到指定的 
queue。
  1. void average_async(int *data, size_t len,  
  2.  
  3. dispatch_queue_t queue, void (^block)(int))  
  4. {  
  5. // Retain the queue provided by the user to make  
  6. // sure it does not disappear before the completion  
  7. // block can be called.  
  8. dispatch_retain(queue);  
  9. // Do the work on the default concurrent queue and then  
  10. // call the user-provided block with the results.  
  11. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
  12. int avg = average(data, len);  
  13. dispatch_async(queue, ^{ block(avg);}); // completion block  
  14. // Release the user-provided queue when done  
  15. dispatch_release(queue);  
  16. });  
Dispatch Queue和线程安全性
 
Dispatch queue 
本身是线程安全的。换句话说,你可以在应用的任意线程 中提交任务到 
dispatch queue,不需要使用锁或其它同步机制。
 
不要在执行任务代码中调用 
dispatch_sync 
函数调度相同的 
queue,这样 
做会死锁这个 
queue。如果你需要 dispatch 
到当前 
queue,需要使
用 
dispatch_async 
函数异步调度
 
避免在提交到 
dispatch queue 
的任务中获得锁,虽然在任务中使用锁是安 全的,但在请求锁时,如果锁不可用,可能会完全阻塞串行 
queue。类似 
的,并发 
queue 
等待锁也可能阻止其它任务的执行。如果代码需要同步, 就使用串行 
dispatch queue。
 
虽然可以获得运行任务的底层线程的信息,最好不要这样做。
从描述符中读写数据

读写数据时,你总是应该配置描述符使用非阻塞操作,虽然你可以使

用 
dispatch_source_get_data 
函数查看当前有多少数据可读(或可用空间可写),但在你调用它和实 际读取数据之间,可用的数据数量可能会发生变化。如果底层文件被截断,或发 生网络错误,从描述符中读取会阻塞当前线程,停止在事件处理器中间并阻止 
dispatch queue 
去执行其它任务。对于串行 
queue,这样还可能会死锁,即使是 
并发 
queue,也会减少 queue 
能够执行的任务数
 
监测信号
如果你只是想信号到达时得到通知, 并不想实际地处理该信号,可以使用信号 
dispatch source 
来异步处理信号。
信号 
dispatch source 
不能替代 
sigaction 
函数提供的同步信号处理机制。同步 信号处理器可以捕获一个信号,并阻止它中止应用。而信号 
dispatch source 
只允 许你监测信号的到达。此外,你不能使用信号 
dispatch source 
获取所有类型的信 号,如 
SIGILL, SIGBUS, SIGSEGV 
信号。
 

使用 dispatch queue 执行的 block 对象不能调用以下函数:

pthread_detachpthread_cancel 
pthread_join
pthread_kill 
pthread_exit

任务运行时修改线程状态是可以的,但你必须还原线程原来的状态。 只要你记得还原线程的状态,下面函数是安全的:

pthread_setcancelstate
pthread_setcanceltype 
pthread_setschedparam 
pthread_sigmask 
pthread_setspecific

特定 block 的执行线程可能在多次调用间会发生变化,因此应用不应 该依赖于以下函数返回的信息:

pthread_self 
pthread_getschedparam 
pthread_get_stacksize_np 
pthread_get_stackaddr_np 
pthread_mach_thread_np 
pthread_from_mach_thread_np 
pthread_getspecific
Block 
必须捕获和禁止任何语言级的异常,Block 执行期间的其它错 误也应该由 
block 
处理,或者通知应用。

转载于:https://my.oschina.net/dake/blog/196745

你可能感兴趣的文章
画廊视图(Gallery)的功能和用法
查看>>
自己动手编写一个VS插件(七)
查看>>
Android里面的Toast
查看>>
mysql双机热备的实现
查看>>
前加绩中国、信雅达高级全栈工程师:吴劲松
查看>>
-bash: pod: command not found 解决办法
查看>>
GCD hdu1695容斥原理
查看>>
Node.js:回调函数
查看>>
python 发送邮件 <QQ+腾讯企业邮箱>
查看>>
细数JDK里的设计模式
查看>>
Linux~上部署.net MVC出现的问题与解决
查看>>
DDD~充血模型和失血模型
查看>>
android DPI与分辨率的关系及计算方式
查看>>
forward_list
查看>>
伪分布式网络爬虫框架的设计与自定义实现(一)
查看>>
解决npm ERR! Unexpected end of JSON input while parsing near的方法汇总
查看>>
MySQL 入门
查看>>
js的操作及css样式
查看>>
bootstrapValidator关于js,jquery动态赋值不触发验证(不能捕获“程序赋值事件”)解决办法...
查看>>
数据库设计基础>范式
查看>>