第四十篇:GCD 多线程

2024-05-12 20:08
文章标签 多线程 第四十 gcd

本文主要是介绍第四十篇: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。


2main:(可选)这个方法通常用来实现 operation 对象相关联的任务。尽管你可以在 start 方法中执行任务,使用 main 来实现任务可让你的代码更加清晰地分离代码和任务代码。


3 isExecuting 与 isFinished:(必须)并发操作负责设置自己的执行环境,并向外部的 client 报告执行环境的状态。因此并发操作必须维护某些状态信息,以知道是否正在执行任务,是否已经完成任务。使用这两个方法报告自己的状态。

这两个方法必须能够在其他多个线程中同时调用。另外这些方法报告自己的状态变化 时,还需为这些相应的 key path 产生适当的 KVO 通知。


4isConcurrent:(必须 )标识一个操作是否并发 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次的代码(这里面默认是线程安全的)
});


1) dispatch_barrier_async 栅栏用法
- (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 多线程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/983617

相关文章

SpringBoot分段处理List集合多线程批量插入数据方式

《SpringBoot分段处理List集合多线程批量插入数据方式》文章介绍如何处理大数据量List批量插入数据库的优化方案:通过拆分List并分配独立线程处理,结合Spring线程池与异步方法提升效率... 目录项目场景解决方案1.实体类2.Mapper3.spring容器注入线程池bejsan对象4.创建

Python多线程实现大文件快速下载的代码实现

《Python多线程实现大文件快速下载的代码实现》在互联网时代,文件下载是日常操作之一,尤其是大文件,然而,网络条件不稳定或带宽有限时,下载速度会变得很慢,本文将介绍如何使用Python实现多线程下载... 目录引言一、多线程下载原理二、python实现多线程下载代码说明:三、实战案例四、注意事项五、总结引

Python多线程应用中的卡死问题优化方案指南

《Python多线程应用中的卡死问题优化方案指南》在利用Python语言开发某查询软件时,遇到了点击搜索按钮后软件卡死的问题,本文将简单分析一下出现的原因以及对应的优化方案,希望对大家有所帮助... 目录问题描述优化方案1. 网络请求优化2. 多线程架构优化3. 全局异常处理4. 配置管理优化优化效果1.

Qt中实现多线程导出数据功能的四种方式小结

《Qt中实现多线程导出数据功能的四种方式小结》在以往的项目开发中,在很多地方用到了多线程,本文将记录下在Qt开发中用到的多线程技术实现方法,以导出指定范围的数字到txt文件为例,展示多线程不同的实现方... 目录前言导出文件的示例工具类QThreadQObject的moveToThread方法实现多线程QC

RabbitMQ消费端单线程与多线程案例讲解

《RabbitMQ消费端单线程与多线程案例讲解》文章解析RabbitMQ消费端单线程与多线程处理机制,说明concurrency控制消费者数量,max-concurrency控制最大线程数,prefe... 目录 一、基础概念详细解释:举个例子:✅ 单消费者 + 单线程消费❌ 单消费者 + 多线程消费❌ 多

Javaee多线程之进程和线程之间的区别和联系(最新整理)

《Javaee多线程之进程和线程之间的区别和联系(最新整理)》进程是资源分配单位,线程是调度执行单位,共享资源更高效,创建线程五种方式:继承Thread、Runnable接口、匿名类、lambda,r... 目录进程和线程进程线程进程和线程的区别创建线程的五种写法继承Thread,重写run实现Runnab

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

python多线程并发测试过程

《python多线程并发测试过程》:本文主要介绍python多线程并发测试过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、并发与并行?二、同步与异步的概念?三、线程与进程的区别?需求1:多线程执行不同任务需求2:多线程执行相同任务总结一、并发与并行?1、

Python多进程、多线程、协程典型示例解析(最新推荐)

《Python多进程、多线程、协程典型示例解析(最新推荐)》:本文主要介绍Python多进程、多线程、协程典型示例解析(最新推荐),本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定... 目录一、multiprocessing(多进程)1. 模块简介2. 案例详解:并行计算平方和3. 实现逻

Java使用多线程处理未知任务数的方案介绍

《Java使用多线程处理未知任务数的方案介绍》这篇文章主要为大家详细介绍了Java如何使用多线程实现处理未知任务数,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 知道任务个数,你可以定义好线程数规则,生成线程数去跑代码说明:1.虚拟线程池:使用 Executors.newVir