Cocoa Touch(四): 多线程GCD, NSObject, NSThread, NSOperationQueue
多线程的重要性不必多言,现代操作系统不可能离开进程线程的抽象。具体到ios应用,我们只能在一个进程中管理线程,主线程不应该去执行非常耗时间的后台操作导致出现卡机现象,后台的事情交给后台线程来完成。
Grand Central Dispatch
GCD组件包含两大部分,第一个部分是提供了C语言线程同步互斥接口,第二个部分就是dispatch队列。
dispatch block的执行最终都会放进某个队列中去进行,它类似NSOperationQueue但更复杂也更强大,并且可以嵌套使用。所以说,结合block实现的GCD,把函数闭包(Closure)的特性发挥得淋漓尽致。
dispatch队列的生成可以有这几种方式:
1. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.serial", DISPATCH_QUEUE_SERIAL); //生成一个串行队列,队列中的block按照先进先出(FIFO)的顺序去执行,实际上为单线程执行。第一个参数是队列的名称,在调试程序时会非常有用,所有尽量不要重名了。
2. dispatch_queue_t queue = dispatch_queue_create("com.dispatch.concurrent", DISPATCH_QUEUE_CONCURRENT); //生成一个并发执行队列,block被分发到多个线程去执行
3. dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); //获得程序进程缺省产生的并发队列,可设定优先级来选择高、中、低三个优先级队列。由于是系统默认生成的,所以无法调用dispatch_resume()和dispatch_suspend()来控制执行继续或中断。需要注意的是,三个队列不代表三个线程,可能会有更多的线程。并发队列可以根据实际情况来自动产生合理的线程数,也可理解为dispatch队列实现了一个线程池的管理,对于程序逻辑是透明的。
官网文档解释说共有三个并发队列,但实际还有一个更低优先级的队列,设置优先级为DISPATCH_QUEUE_PRIORITY_BACKGROUND。Xcode调试时可以观察到正在使用的各个dispatch队列。
4. dispatch_queue_t queue = dispatch_get_main_queue(); //获得主线程的dispatch队列,注意这个队列中的任务需要被主线程完成。同样无法控制主线程dispatch队列的执行继续或中断。
接下来我们可以使用dispatch_async或dispatch_sync函数来加载需要运行的block。
dispatch_async(queue, ^{
//block具体代码
}); //异步执行block,函数立即返回
dispatch_sync(queue, ^{
//block具体代码
}); //同步执行block,函数不返回,一直等到block执行完毕。编译器会根据实际情况优化代码,所以有时候你会发现block其实还在当前线程上执行,并没用产生新线程。
实际编程经验告诉我们,尽可能避免使用dispatch_sync,嵌套使用时还容易引起程序死锁。
如果queue1是一个串行队列的话,这段代码立即产生死锁:
dispatch_sync(queue1, ^{
dispatch_sync(queue1, ^{
......
});
......
});
在主线程当中,为什么下面代码也肯定死锁:
dispatch_sync(dispatch_get_main_queue(), ^{
......
});
那实际运用中,一般可以用dispatch这样来写,常见的网络请求数据多线程执行模型:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//子线程中开始网络请求数据
//更新数据模型
dispatch_sync(dispatch_get_main_queue(), ^{
//在主线程中更新UI代码
});
});
程序的后台运行和UI更新代码紧凑,代码逻辑一目了然。
dispatch队列是线程安全的,可以利用串行队列实现锁的功能。比如多线程写同一数据库,需要保持写入的顺序和每次写入的完整性,简单地利用串行队列即可实现:
dispatch_queue_t queue1 = dispatch_queue_create("com.dispatch.writedb", DISPATCH_QUEUE_SERIAL);
- (void)writeDB:(NSData *)data
{
dispatch_async(queue1, ^{
//write database
});
}
下一次调用writeDB:必须等到上次调用完成后才能进行,保证writeDB:方法是线程安全的。
dispatch队列还实现其它一些常用函数,包括:
void dispatch_apply(size_t iterations, dispatch_queue_t queue, void (^block)(size_t)); //重复执行block,需要注意的是这个方法是同步返回,也就是说等到所有block执行完毕才返回,如需异步返回则嵌套在dispatch_async中来使用。多个block的运行是否并发或串行执行也依赖queue的是否并发或串行。
void dispatch_barrier_async(dispatch_queue_t queue, dispatch_block_t block); //这个函数可以设置同步执行的block,它会等到在它加入队列之前的block执行完毕后,才开始执行。在它之后加入队列的block,则等到这个block执行完毕后才开始执行。
void dispatch_barrier_sync(dispatch_queue_t queue, dispatch_block_t block); //同上,除了它是同步返回函数
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block); //延迟执行block
最后再来看看dispatch队列的一个很有特色的函数:
void dispatch_set_target_queue(dispatch_object_t object, dispatch_queue_t queue);
它会把需要执行的任务对象指定到不同的队列中去处理,这个任务对象可以是dispatch队列,也可以是dispatch源。而且这个过程可以是动态的,可以实现队列的动态调度管理等等。比如说有两个队列dispatchA和dispatchB,这时把dispatchA指派到dispatchB:
dispatch_set_target_queue(dispatchA, dispatchB);
那么dispatchA上还未运行的block会在dispatchB上运行。这时如果暂停dispatchA运行:
dispatch_suspend(dispatchA);
则只会暂停dispatchA上原来的block的执行,dispatchB的block则不受影响。而如果暂停dispatchB的运行,则会暂停dispatchA的运行。
这里只简单举个例子,说明dispatch队列运行的灵活性,在实际应用中你会逐步发掘出它的潜力。
优点:简单方便,直接捕获变量进入block(只读访问,除非用__block声明)。
缺点:dispatch队列暂时不支持cancel(取消),没有实现dispatch_cancel()函数,不像NSOperationQueue。
ps: GCD所包含的方法并不限于任务队列,还提供了 dispatch_semaphore_signal/dispatch_semaphore_wait 这样的C语言接口(所有C语言代码都能用OC编译器编译),并不会强制要求程序员一定要面向对象,避免了很多繁琐步骤。
ps: !!! 重要的一点:block并不是被主线程所执行,如果其它的线程在执行这个block是,调用了类似 _webView = [[UIWebView alloc] init]这样的语句,那么就会Crash:
Tried to obtain the web lock from a thread other than the main thread or the web thread.
This may be a result of calling to UIKit from a secondary thread. Crashing now...
解决办法就是,通过performSelectorOnMainThread或者GCD dispatch_的方式强制让某些语句被主线程执行,或者通过信号量实现同步的方式来发送网络请求,从而保证由主线程来调用所有涉及UI的语句。
NSObject
所有继承于NSObject类的类实例,都可以调用启动多线程的方法。
以下3个函数是多线程的方法:
- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg,创建一个真正的新线程,不过无法引用到这个线程
[self performSelectorOnMainThread:@selector(updateImage:) withObject:data waitUntilDone:YES]; 通知主线程执行操作,一般用于更新界面
[self performSelector:@selector(run) onThread:thread withObject:nil waitUntilDone:YES]; 等待selector指定的函数被某个线程执行完成后,当前线程再继续当前任务
以下2个函数实际上不是多线程:
[self performSelector:@selector(run) withObject:nil];等待selector指定的函数被当前线程执行完成后,当前线程再继续当前任务
performSelector:withObject:afterDelay:当前线程执行完成后,再启动线程去执行selector所选择的方法
Objective-C中调用函数的方法是“消息传递”,这个和普通的函数调用的区别是,你可以随时对一个对象传递任何消息,而不需要在编译的时候声明这些方法。所以Objective-C可以在runtime的时候传递人和消息。
优点:简单方便,并且允许在运行时动态调用一个对象的任意方法
缺点:如果需要传递参数,不如GCD
NSThread
NSThread类是轻量级的多线程开发的类,使用起来也并不复杂,但是使用NSThread需要自己管理线程生命周期。NSThread中封装了方法:
+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument 创建一个真实的新线程,不过无法引用到这个线程
- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument 创建一个线程对象,可以引用到一个新线程,调用start方法创建真实的线程。
[thread start]; 创建一个真实的线程,并且把线程对象thread作为这个线程的引用
[thread cancel]; 取消对象thread所引用的那个线程
这个类命名空间下还提供了一些实用的类方法:
NSThread *current = [NSThread currentThread]; 获取当前线程
NSThread *main = [NSThread mainThread]; 获取主线程
[NSThread sleepForTimeInterval:2]; 或 [NSThread sleepUntilDate:date]; 暂停正在执行这段代码的进程,也就是当前进程
优点:以面对对象的观点引用线程,方便线程生命周期的管理和多线程之间的同步;这个类可以配合performselector方法使用;提供了一些实用的类方法,如获取当前线程的引用、暂停当前线程等。
缺点:需要定义单独的线程类来代表新的线程,较为繁琐,不够直接
NSOperation
我们先直接通过代码来领会一个任务队列。
invocation pperation(通过selector指定任务)
-(void)loadImageWithMultiThread{
/*创建一个调用操作
object:调用方法参数
*/
NSInvocationOperation *invocationOperation=[[NSInvocationOperation alloc]initWithTarget:self selector:@selector(loadImage) object:nil];
//进程通过start方法后才能启动操作,但是注意如果直接调用start方法,则此操作会在主线程中调用,一般不会这么操作,而是添加到NSOperationQueue中
// [invocationOperation start]; //创建操作队列
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
//注意添加到操作队后,队列会开启一个线程执行此操作
[operationQueue addOperation:invocationOperation];
}
block operation(通过block指定任务)
NSOperationQueue *operationQueue=[[NSOperationQueue alloc]init];
operationQueue.maxConcurrentOperationCount=;//设置最大并发线程数
[operationQueue addOperationWithBlock:^{
[self loadImage:[NSNumber numberWithInt:i]];
}];
//更新UI界面,此处调用了主线程队列的方法(mainQueue是UI主线程)
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
[self updateImageWithData:data andIndex:i];
}];
概念上,一个任务队列operation queue相当于一个由很多线程和任务组成的池。在一些应用场景之下,比如当需要下载很多张图片,可以把下载任务和相应的线程统一交给operation queue来管理,方便了操作。
一个NSOperation的实例定义了一个任务,可以通过调用start方法在当前线程启动它对应的任务,不过一般不这么做,而是把operation加入Operation queue中统一管理。对于invocation operation和block operation,两种方式本质没有区别,但是是后者使用Block形式进行代码组织,传参容易,使用相对方便。
优点:统一管理大量的任务和线程,适用于下载大量图片等场景。
多线程的应用
1、操作数据模型
多个任务同时操作数据模型,线程安全需要仔细思考,对于数据模型对象的操作需要用@synchronized。
2、网络请求
网络请求是多线程的重要应用,下一篇博客会讲。
Cocoa Touch(四): 多线程GCD, NSObject, NSThread, NSOperationQueue的更多相关文章
- ios中多线程GCD NSOperation NSThread 相关的操作解析
//1.GCD 继承自C语言 优点 简单方便 //开启一个子线程处理耗时的操作 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIO ...
- 多线程&NSObject&NSThread&NSOperation&GCD
1.NSThread 每个NSThread对象对应一个线程,量级较轻(真正的多线程) 以下两点是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题 2.NSOperation/NS ...
- 修改版: 小伙,多线程(GCD)看我就够了,骗你没好处!
多线程(英语:multithreading),是指从软件或者硬件上实现多个线程并发执行的技术.具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能.具有这种能力的系 ...
- iOS多线程编程之NSThread的使用
目录(?)[-] 简介 iOS有三种多线程编程的技术分别是 三种方式的有缺点介绍 NSThread的使用 NSThread 有两种直接创建方式 参数的意义 PS不显式创建线程的方法 下载图片的例子 ...
- iOS多线程编程之NSThread的使用(转)
本文由http://blog.csdn.net/totogo2010/原创 1.简介: 1.1 iOS有三种多线程编程的技术,分别是: 1..NSThread 2.Cocoa NSOperation ...
- [转]iOS多线程编程之NSThread的使用
1.简介: 1.1 iOS有三种多线程编程的技术,分别是: 1..NSThread 2.Cocoa NSOperation (iOS多线程编程之NSOperation和NSOperationQueue ...
- iOS多线程编程之NSThread的使用(转载)
1.简介: 1.1 iOS有三种多线程编程的技术,分别是: 1.NSThread 2.Cocoa NSOperation (iOS多线程编程之NSOperation和NSOperationQueue的 ...
- iOS 多线程学习笔记 —— NSThread
本文复制.参考自文章:iOS多线程编程之NSThread的使用 ,主要为了加强个人对知识的理解和记忆,不做他用.原作者声明: 著作权声明:本文由http://blog.csdn.net/totogo ...
- 多线程开发之一 NSThread
每个 iOS 应用程序都有个专门用来更新显示 UI 界面.处理用户的触摸事件的主线程,因此不能将其他太耗时的操作放在主线程中执行,不然会造成主线程堵塞(出现卡机现象),带来不好的用户体验. 一般的解决 ...
随机推荐
- REST风格
1)Representational State Transfer,表述性状态转移,是一种软件架构风格 2)实现步骤 第一步:修改URL 例:http://localhost:8090/SMBMS_C ...
- Ext.js 之MVC
Ext.js 4.0之MVC
- pthread访问调用信号线程的掩码(pthread_sigmask )
掩码: 信号掩码 在POSIX下,每个进程有一个信号掩码(signal mask).简单地说,信号掩码是一个"位图",其中每一位都对应着一种信号.如果位图中的某一位为1,就表示在执 ...
- Git Flow分支策略
就像代码需要代码规范一样,代码管理同样需要一个清晰的流程和规范 Vincent Driessen 同学为了解决这个问题提出了 A Successful Git Branching Model 下面是G ...
- docker挂载本地目录和数据卷容器
1.docker挂载本地目录 docker可以支持把一个宿主机上的目录挂载到镜像里. 交互模式运行docker run -it -v /home/dock/Downloads:/usr/Downloa ...
- 洛谷3343(ZJOI2015)地震后的幻想乡
题目:https://www.luogu.org/problemnew/show/P3343 1.那个时间与边的大小排名有关,所以需要求一下最大边的期望排名就行. 2.期望排名是这样算的:(排名为1的 ...
- C#使用OpcNetApi.dll和OpcNetApi.Com.dll操作OPC
本人学习了一下.Net,恰好,51自学网,又要用这个.而网上很多VC6,VB6,VB .Net的但,很少C#的.现在研究一下,给出例子: 测试平台,是VS2008,KEPServer,OpcNetAp ...
- ruby的代码风格
http://stylesror.github.io/ 大部分同意,有小部分,不敢苟同.
- 关于filter web api mvc 权限验证 这里说的够详细了。。。
参考:http://www.cnblogs.com/willick/p/3331520.html Filter(筛选器)是基于AOP(面向方面编程)的设计,它的作用是对MVC框架处理客户端请求注入额外 ...
- Linux嵌入式内核模块程序设计
1.环境搭建 vmware+Fedora 2.创建一个Hello文件 [root@localhost ~]# mkdir Hello 3.在Hello里面创建 hello.c 和 Makefile 两 ...