本文主要是介绍第四十篇:GCD 多线程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、Operation Objects
1、相关类
1)NSOperation 基类:
基类,用来自定义子类 operation object 。继承 NSOperation 可以完全控制 operation object 的实现,包括修改操作执行和状态报告的方式。
2)NSInvocationOperation:
可以直接使用的类,基于应用的一个对象和 selector 来创建 operation object 。如果你已经有现在的方法来执行需要的任务,就可能使用该类。
3)NSBlockOperation:
可以直接使用的类,用来并发地执行一个或多个 block 对象,operation object 使用 “组” 的语义来执行多个block对象。所有相关的 block 都执行完之后,operation object 才算完成。
2、所有 operation object 都支持的关键特性
1)支持建立基于图的 operation objects 依赖。可以阻止某个operation 运行,直到它支持的所有 operation 都已经完成。
2)支持可选的 completion block ,在 operation 的主任务完成后调用。
3)支持应用使用 KVO 通知来监控 operation 的执行状态。
4)支持 operation 的 优先级,从而影响相关的执行顺序。
5)支持取消,允许你中止正在执行的任务。
3、一些对象方法
1) start :(必需)所有并发操作都必需覆盖这个方法,以自定义的实现来替换默认行为。手动执行一个操作时,你会调用 start 方法。因为你对这个方法的实现是操作的起点,设置一个线程或其它执行环境,来执行你的任务,你的实现在任何时候都绝不能调用 super。
2)main:(可选)这个方法通常用来实现 operation 对象相关联的任务。尽管你可以在 start 方法中执行任务,使用 main 来实现任务可让你的代码更加清晰地分离代码和任务代码。
3) isExecuting 与 isFinished:(必须)并发操作负责设置自己的执行环境,并向外部的 client 报告执行环境的状态。因此并发操作必须维护某些状态信息,以知道是否正在执行任务,是否已经完成任务。使用这两个方法报告自己的状态。
这两个方法必须能够在其他多个线程中同时调用。另外这些方法报告自己的状态变化 时,还需为这些相应的 key path 产生适当的 KVO 通知。
4)isConcurrent:(必须 )标识一个操作是否并发 operation,覆盖这个方法并返回一个 YES 。
5) setThreadPriority: 设置优先级 范围(0.0 ~ 1.0 ,默认为 0.5)
6) setCompletionBlock: 设置完成后调用的 block
4、管理内存使用 NSAutoReleasePoll
二、Operation Queue
1、NSOperationQueue
1)负责管理Operation Object ,设计是用于并发执行 Operations ,你也可以强制单个queue 一次只能执行一个 Operation 。使用 setMaxConcurrentOperationCount: 方法可以配置 operation queue 的最大并发操作数量。设为 1 就表示 queue 每次只能执行一个操作。不过 operation 执行顺序仍然依赖于其它因素,像操作是否准备好和优先级等。因此串行化的 operation queue 并不等同于 GCD 中的串行 dispatch queue。
2)一些方法:
2.1) addOperation: 方法添加一个 operation 到 queue 。
2.2) addOperations:waitUntilFinished: 方法添加一组 operations 到 queue。
2.3)addOperationWithBlock: 方法添加 block 对象到 queue 。
2.4) waitUntilAllOperationsAreFinished 方法可以同时等待一个 queue 中所有操作。在等待中还是可以向 queue 添加 Operation 加长线程的等待时间。
2.5) setMaxConcurrentOperationCount: 方法可以配置 operation queue 的最大并发操作数量。
2.6) setSuspended: 挂起 或 继续 queue 中的 Operations ,暂停等待中的任务。正在执行的 Operation 不会被暂停。
Operation 添加到 queue 之后,通常短时间内就会行到运行。但是如果存在依赖,或者 Operations 挂起等原因,也可能需要等待。
注意: Operation 添加到 queue 之后 ,绝对不要修改 Operations 对象。因为 Operations 可能会在任何时候运行,因此改变依赖或数据会产生不利的影响。但可以通过NSOperation 的方法来查看操作的状态,是否在运行、等待运行、已经完成等。
三、Dispatch Queues
1、几个关键点
1)dispatch queues 相对于其它的 dispatch queues并发地执行任务,串行化任务只能在同一个dispatch queues中实现。
2)系统决定了同时能够执行的任务数量,应用在 100 个不同的 queues 中启动 100 个任务,并不代表 100 个任务全部都在并发地执行(除非系统拥有 100 或 更多的核)。
3)系统在选择执行哪个任务时,会先考虑 queue 的优先级。
4)queue 中的任务必须在任何时候都准备好运行,注意这点和 Operation 对象相同。
5)private dispatch queue 是引用 计数的对象,你的代码中需要 retain 这些 queue ,另外 dispatch source 也可能添加到一个 queue,从而增加 retain 的计数。因此你必须确保所有的 dispatch queue 都被取消,而且适当地调用 release 。
2、创建 queue
1. 创建串行 queue:dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
// 串行队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);
// 并发队列的创建方法
dispatch_queue_t queue= dispatch_queue_create("test.queue", DISPATCH_QUEUE_CONCURRENT);
两个参数分别是 queue 名 和 一组 queue 属性
// 串行队列的创建方法2. 获取公共 Queue1)使用 dispatch_get_current_queue 函数作为调试用途。在 block 对象 调用这个函数会返回 block 提交到的 queue。在 block 之外调用这个函数会返回应用的默认并发 queue 。2)使用 dispatch_get_main_queue 函数获取主线程关联的串行 dispatch queue 。Cocoa 应用、调用了 disptch_main 函数或配制了 run loop 的应用,会自动创建这个 queue。3)使用 dispatch_get_global_queue 来获得共享的并发 queue。
3、内存管理
1)使用 dispatch_retain 和 dispatch_release 配套使用增加和减少引用计数。
2)dispatch_set_finalizer_f(queue,&functionName) 指定 queue 计数器为0时调用清理函数 function 方法 。
4、方法用途
1. dispatch_apply( count , queue , ^(size_t i) { 做一些事 }) ; queue 如果是并发性质 那么 block 中就是可并发的,如果串行性质 那么 block 一次次执行。注意:无论是哪种性质都是先执行完 dispatch_apply 所有内容才往后执行。2. dispatch_suspend 挂起 dispatch queue ,queue 引用计数增加 。当引用计数大于 0 时,queue 保持挂起状态。异步;只在执行 block 之前生效;挂起一个 queue 不会导致正在执行的 block 停止。3. dispatch_resume 继续 dispatch queue ,queue 引用计数减少。异步;只在执行 block 之前生效。4. dispatch_async 异步执行,只有与并发(DISPATCH_QUEUE_CONCURRENT) queue 结合使用才能并发执行任务。有一点注意:把所有的任务添加设置完成后才开始执行的,而不是添加一个立即执行。5. dispatch_sync 同步执行,不管与并发queue 或 同串行 queue 结合使用都是同步执行。6. dispatch_barrier_async 栅栏方法,可隔开在同一并发 queue 里的任务分段并发执行任务,如下:7. dispatch_after GCD的延时并发执行方法,如:dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{// 2秒后异步执行这里的代码...NSLog(@"run-----");
});8. dispatch_once GCD一次性代码(即 只执行一次)。在创建单例 或者 有整个程序在执行的过程中只执行一次的代码时,就用到 dispathc_once 方法,保证某段代码只被执行 1 次。static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{// 只执行1次的代码(这里面默认是线程安全的)
});
- (void)barrier
{dispatch_queue_t queue = dispatch_queue_create("12312312", DISPATCH_QUEUE_CONCURRENT);dispatch_async(queue, ^{NSLog(@"----1-----%@", [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(@"----2-----%@", [NSThread currentThread]);});dispatch_barrier_async(queue, ^{NSLog(@"----barrier-----%@", [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(@"----3-----%@", [NSThread currentThread]);});dispatch_async(queue, ^{NSLog(@"----4-----%@", [NSThread currentThread]);});
}
输出结果:
2016-09-03 19:35:51.271 GCD[11750:1914724] ----1-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----2-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.272 GCD[11750:1914722] ----barrier-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914722] ----3-----<NSThread: 0x7fb182423fd0>{number = 3, name = (null)}
2016-09-03 19:35:51.273 GCD[11750:1914724] ----4-----<NSThread: 0x7fb1826047b0>{number = 2, name = (null)}
2)dispatch_group 用法
dispatch_group_t group = dispatch_group_create();// 下面两个任务异步执行(并发)dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i = 0 ; i < 100; i++) {NSLog(@" 1 >>>> %d >>>> ",i);}});dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{for (int i = 0 ; i < 100; i++) {NSLog(@" 2 >>>> %d >>>> ",i);}});// 当上面两个任务完成后会自动调用下面 GCD 方法dispatch_group_notify(group, dispatch_get_main_queue(), ^{NSLog(@"更新 UI 在 主线程 %@",[NSThread currentThread]);});
5、线程安全性
1)dispatch queue 本身是线程安全的。你可以在应用的任意线程中提交任务到 dispatch queue ,不需要使用锁或其他同步机制。
2)不要在执行任务代码中调用 dispatch_sync 函数调度相同的 queue ,这样会死锁这个 queue。如果你需要 dispatch 到当前的 queue,需要使用 dispatch_async 函数异步调度。
3)避免在提交到 dispatch queue 的任务中获得锁,虽然在任务中使用锁是安全的,但在请求锁时,如果锁不可用,可能会完全阻塞串行 queue。类似的,并发 queue 等待锁也可能会阻止其它任务的执行。如果代码需要同步就使用串行 dispatch queue 。
4)虽然可以获得运行任务的底层线程的信息,最好不要这样做。
四、Dispatch Source
创建dispatch source :dispatch_source_t source = dispatch_source_create(dispatch_source_type_t type, uintptr_t handle, unsigned long mask, dispatch_queue_t queue)参数意义:
type : dispatch 源可处理的事件;
handle:可以理解为句柄、索引或id,假如要监听进程,需要传入进程的ID;
mask: 可以理解为描述,提供更详细的描述,让它知道具体要监听什么;
queue: 自定义源需要的一个队列,用来处理所有的响应句柄(block)。type 可处理的所有事件:DISPATCH_SOURCE_TYPE_DATA_ADD 自定义的事件,变量增加
DISPATCH_SOURCE_TYPE_DATA_OR 自定义的事件,变量OR
DISPATCH_SOURCE_TYPE_MACH_SEND MACH端口发送
DISPATCH_SOURCE_TYPE_MACH_RECV MACH端口接收
DISPATCH_SOURCE_TYPE_PROC 进程监听,如进程的退出、创建一个或更多的子线程、进程收到UNIX信号
DISPATCH_SOURCE_TYPE_READ IO操作,如对文件的操作、socket操作的读响应
DISPATCH_SOURCE_TYPE_SIGNAL 接收到UNIX信号时响应
DISPATCH_SOURCE_TYPE_TIMER 定时器
DISPATCH_SOURCE_TYPE_VNODE 文件状态监听,文件被删除、移动、重命名
DISPATCH_SOURCE_TYPE_WRITE IO操作,如对文件的操作、socket操作的写响应
1)一些函数
dispatch_suspend(queue) //挂起队列dispatch_resume(source) //分派源创建时默认处于暂停状态,在分派源分派处理程序之前必须先恢复dispatch_source_merge_data //向分派源发送事件,需要注意的是,不可以传递0值(事件不会被触发),同样也不可以传递负数。dispatch_source_set_event_handler //设置响应分派源事件的block,在分派源指定的队列上运行dispatch_source_get_data //得到分派源的数据uintptr_t dispatch_source_get_handle(dispatch_source_t source); //得到dispatch源创建,即调用dispatch_source_create的第二个参数unsigned long dispatch_source_get_mask(dispatch_source_t source); //得到dispatch源创建,即调用dispatch_source_create的第三个参数void dispatch_source_cancel(dispatch_source_t source); //取消dispatch源的事件处理--即不再调用block。如果调用dispatch_suspend只是暂停dispatch源。long dispatch_source_testcancel(dispatch_source_t source); //检测是否dispatch源被取消,如果返回非0值则表明dispatch源已经被取消void dispatch_source_set_cancel_handler(dispatch_source_t source, dispatch_block_t cancel_handler); //dispatch源取消时调用的block,一般用于关闭文件或socket等,释放相关资源void dispatch_source_set_registration_handler(dispatch_source_t source, dispatch_block_t registration_handler); //可用于设置dispatch源启动时调用block,调用完成后即释放这个block。也可在dispatch源运行当中随时调用这个函数。
2)样例
dispatch_source_t source = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_global_queue(0, 0));dispatch_source_set_event_handler(source, ^{dispatch_sync(dispatch_get_main_queue(), ^{//更新UI});});// 开启 source 源dispatch_resume(source);dispatch_async(dispatch_get_global_queue(0, 0), ^{//网络请求//通知队列,触发 dispatch_source_set_event_handler 设置的 block 。dispatch_source_merge_data(source, 1); });
五、RunLoop
1、RunLoop 相关几个类的含意及关系
1)CFRunLoopRef : 代表 RunLoop 对象;
2)CFRunLoopModeRef : 代表 RunLoop 运行模式;
3)CFRunLoopSourceRef : 就是 RunLoop 模型的 输入源 或 事件源;
4)CFRunLoopTimerRef : 就是 RunLoop 模型的定时源;
5)CFRunLoopObserverRef : 观察者,能够监听到 RunLoop 状态改变。
2、关系图
一个RunLoop对象(CFRunLoopRef)中包含若干个运行模式(CFRunLoopModeRef)。而每一个运行模式下又包含若干个输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)、观察者(CFRunLoopObserverRef)。
1)每次 RunLoop 启动时,只能指定其中一个运行模式(CFRunLoopModeRef),这个运行模式被称作 currentMode。
2)如果需要切换运行模式(CFRunLoopModeRef),只能先退出Loop,再重新定一个运行模式进入。
3)这样做主要是为了隔开不同组的 输入源(CFRunLoopSourceRef)、定时源(CFRunLoopTimerRef)和 观察者(CFRunLoopObserverRef),让其互不影响。
3、CFRunLoopRef
CFRunLoopRef 就是 Core Foundation 框架下的 RunLoop 对象类。
获取对象方式:
1) Core Foundation
(1.1)CFRunLoopGetCurrent( ); // 获取当前线程的 RunLoop 对象
(1.2)CFRunLoopGetMain( ); // 获取主线程的 RunLoop 对象
2)Foundation
(2.1)[NSRunLoop currentRunLoop ]; // 获取当前线程的 RunLoop 对象
(2.2)[NSRunLoop mainRunLoop] ; // 获取主线程的 RunLoop 对象
4、CFRunLoopModeRef 多种运行模式
1)kCFRunLoopDefaultMode :APP 默认运行模式,通常主线程是在这种模式下运行。
2)UITrackingRunLoopMode :跟踪用户交互事件(用于 UIScrollView 追踪触摸滑动,保证界面在滑动时不受其他 Mode 影响)。
3)UIInitializationRunLoopMode :在刚刚启动 APP 时进入的第一 Mode ,启动完后就不再使用了。
4)GSEventReceiveRunLoopMode :接受系统内部事件,通常不到。
5)kCFRunLoopCommonModes :伪模式,不是一种真正的运行模式(后面会用到)。
5、CFRunLoopTimerRef
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
相当于做了下面的事,自动添加到 RunLoop :NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
6、CFRunLoopSourceRef
7、CFRunLoopObserverRef
1)监听状态种类
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {kCFRunLoopEntry = (1UL << 0), // 即将进入Loop:1kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer:2 kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source:4kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠:32kCFRunLoopAfterWaiting = (1UL << 6), // 即将从休眠中唤醒:64kCFRunLoopExit = (1UL << 7), // 即将从Loop中退出:128kCFRunLoopAllActivities = 0x0FFFFFFFU // 监听全部状态改变
};
2) 示例
- (void)viewDidLoad {[super viewDidLoad];// 创建观察者CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {NSLog(@"监听到RunLoop发生改变---%zd",activity);});// 添加观察者到当前RunLoop中CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);// 释放observer,最后添加完需要释放掉CFRelease(observer);
}
这篇关于第四十篇:GCD 多线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!