iOS的多线程原理、分类与应用

来源:互联网 发布:class javascript mdn 编辑:程序博客网 时间:2024/04/29 22:19
今天查资料才发现,iOS中的线程使用不是无限制的,官方文档给出的资料显示iOS下的主线程堆栈大小是1M,第二个线程开始都是512KB,并且该值不能通过编译器开关或线程API函数来更改。另外只有主线程有直接修改UI的能力。所以也学习并总结下iOS的多线程编程来加深下吧。关于RunLoopgNSThreadNSOperationQueue和NSOperationGCDNSOperationQueue与GCD的对比关于RunLoop首先关于RunLoop,iOS中的RunLoop准确的说是线程中的循环。首先循环体的开始需要检测是否有需要处理的事件,如果有则去处理,如果没有则进入睡眠以节省CPU时间。 所以重点便是这个需要处理的事件,在RunLoop中,需要处理的事件分两类,一种是输入源,一种是定时器。定时器好理解,就是那些需要定时执行的操作;输入源分三类:performSelector源,基于端口(Mach port)的源,以及自定义的源。而RunLoop在每一次循环的开始便去检查这些事件源是否有需要处理的数据,有的话则去处理。系统会自动为应用程序的主线程生成一个与之对应的 run loop 来处理其消息循环。在触摸 UIView 时之所以能够激发 touchesBegan/touchesMoved 等等函数被调用,就是因为应用程序的主线程在 UIApplicationMain 里面有这样一个 run loop 在分发 input 或 timer 事件。而每一个线程都有其对应的RunLoop,但是默认非主线程的RunLoop是没有运行的,需要为RunLoop添加至少一个事件源,然后去run它。一般情况下我们是没有必要去启用线程的RunLoop的,除非你在一个单独的线程中需要长久的检测某个事件。多线程编程iOS中的多线程编程主要分以下三类:1.NSThread;2.NSOperation/NSOperationQueue;3.GCD。后两者其实都是对NSThreads的调用再进行一次封装,以便开发人员更容易使用iOS中的多线程编程。而对于NSOperation/NSOperationQueue和GCD的比较,支持者们意见不太统一,应该适时选择合适的。这里也附上StackOverflow上的讨论(本文后面也有列出大概原因)情况。NSThread优点:NSThread比其他两个轻量级缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销NSThread的使用- (id)initWithTarget:(id)target selector:(SEL)selector object:(id)argument+ (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument第一个是实例方法,第二个是类方法// 类方法[NSThread detachNewThreadSelector:@selector(doSomething:) toTarget:self withObject:nil];// 实例方法的声明NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(doSomething:) object:nil];// 实例方法的调用[myThread start];这两种方式的区别是:前一种一调用就会立即创建一个线程来做事情;而后一种虽然你alloc了也init了,但是要直到我们手动调用 start 启动线程时才会真正去创建线程。这种延迟实现思想在很多跟资源相关的地方都有用到。后一种方式我们还可以在启动线程之前,对线程进行配置,比如设置stack大小,线程优先级。还有一种间接的方式,更加方便,我们甚至不需要显式编写NSThread相关代码。那就是利用NSObject的类方法 performSelectorInBackground:withObject: 来创建一个线程:1[myObj performSelectorInBackground:@selector(myThreadMainMethod) withObject:nil];其效果与NSThread的 detachNewThreadSelector:toTarget:withObject: 是一样的线程同步线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用mutex,lock等。iOS的原子操作函数是以OSAtomic开头的,比如:OSAtomicAdd32, OSAtomicOr32等等。这些函数可以直接使用,因为它们是原子操作。iOS中的mutex对应的是NSLock,它遵循 NSLooking协议,我们可以使用lock, tryLock, lockBeforeData:来加锁,用unLock来解锁。使用示例:BOOL moreToDo = YES;NSLock *theLock = [[NSLock alloc] init];...while (moreToDo) { /* Do another increment of calculation */ /* until there’s no more to do. */ if ([theLock tryLock]) { /* Update display used by all threads. */ [theLock unlock]; }}我们可以使用指令 @synchronized 来简化 NSLock的使用,这样我们就不必显示编写创建NSLock,加锁并解锁相关代码。- (void)myMethod:(id)anObj{ @synchronized(anObj) { // Everything between the braces is protected by the @synchronized directive. }}还有其他的一些锁对象,比如:循环锁NSRecursiveLock,条件锁NSConditionLock,分布式锁NSDistributedLock等等,在这里就不一一介绍了。用NSCodition同步执行的顺序NSCodition是一种特殊类型的锁,我们可以用它来同步操作执行的顺序。它与mutex的区别在于更加精准,等待某个NSCondtion的线程一直被lock,直到其他线程给那个condition发送了信号。下面我们来看使用示例:// 某个线程等待着事情去做,而有没有事情做是由其他线程通知它的[cocoaCondition lock];while (timeToDoWork <= 0) [cocoaCondition wait];timeToDoWork--;// Do real work here.[cocoaCondition unlock];//其他线程发送信号通知上面的线程可以做事情了:[cocoaCondition lock];timeToDoWork++;[cocoaCondition signal];[cocoaCondition unlock];线程间通信线程在运行过程中,可能需要与其它线程进行通信。我们可以使用 NSObject 中的一些方法:// 在应用程序主线程中做事情:performSelectorOnMainThread:withObject:waitUntilDone:performSelectorOnMainThread:withObject:waitUntilDone:modes:// 在指定线程中做事情:performSelector:onThread:withObject:waitUntilDone:performSelector:onThread:withObject:waitUntilDone:modes:// 在当前线程中做事情:performSelector:withObject:afterDelay:performSelector:withObject:afterDelay:inModes:// 取消发送给当前线程的某个消息cancelPreviousPerformRequestsWithTarget:cancelPreviousPerformRequestsWithTarget:selector:object:如在我们在某个线程中下载数据,下载完成之后要通知主线程中更新界面等等,可以使用如下接口:- (void)callMainThreadMethod{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // to do something in your thread job ... [self performSelectorOnMainThread:@selector(updateUI) withObject:nil waitUntilDone:NO]; [pool release];}隐式调用用NSObject的类方法performSelectorInBackground:withObject:创建一个线程:1[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];NSOperationQueue和NSOperation多线程编程是防止主线程堵塞,增加运行效率等等的最佳方法。而原始的多线程方法存在很多的毛病,包括线程锁死等。在Cocoa中,Apple提供了NSOperation这个类,提供了一个优秀的多线程编程方法。NSOperationQueue会建立一个线程管理器,每个加入到线程operation会有序的执行。用NSOperationQueue的过程:建立一个NSOperationQueue的对象建立一个NSOperation的对象将operation加入到NSOperationQueue中release掉operation本次介绍NSOperation的子集,简易方法的NSInvocationOperation:@implementation MyCustomClass- (void)launchTaskWithData:(id)data{ //创建一个NSInvocationOperation对象,并初始化到方法 //在这里,selector参数后的值是你想在另外一个线程中运行的方法(函数,Method) //在这里,object后的值是想传递给前面方法的数据 NSInvocationOperation* theOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(myTaskMethod:) object:data]; // 下面将我们建立的操作“Operation”加入到本地程序的共享队列中(加入后方法就会立刻被执行) // 更多的时候是由我们自己建立“操作”队列 [[MyAppDelegate sharedOperationQueue] addOperation:theOp];}// 这个是真正运行在另外一个线程的“方法”- (void)myTaskMethod:(id)data{ // Perform the task.}@end一个NSOperationQueue操作队列,就相当于一个线程管理器,而非一个线程。因为你可以设置这个线程管理器内可以并行运行的的线程数量等等。下面是建立并初始化一个操作队列:@interface MyViewController : UIViewController { NSOperationQueue *operationQueue; //在头文件中声明该队列}@end@implementation MyViewController- (id)init{ self = [super init]; if (self) { operationQueue = [[NSOperationQueue alloc] init]; //初始化操作队列 [operationQueue setMaxConcurrentOperationCount:1]; //在这里限定了该队列只同时运行一个线程 //这个队列已经可以使用了 } return self;}- (void)dealloc{ [operationQueue release]; //正如Alan经常说的,我们是程序的好公民,需要释放内存! [super dealloc];}@endGCDGCD是一个替代诸如NSThread, NSOperationQueue, NSInvocationOperation等技术的很高效和强大的技术,它看起来象就其它语言的闭包(Closure)一样,但苹果把它叫做blocks。GCD的定义简单GCD的定义有点象函数指针,差别是用 ^ 替代了函数指针的 * 号,如下所示:// 申明变量 (void) (^loggerBlock)(void); // 定义 loggerBlock = ^{ NSLog(@"Hello world"); }; // 调用 loggerBlock();但是大多数时候,我们通常使用内联的方式来定义它,即将它的程序块写在调用的函数里面,例如这样:123dispatch_async(dispatch_get_global_queue(0, 0), ^{ // something });从上面大家可以看出,block有如下特点:程序块可以在代码中以内联的方式来定义。 程序块可以访问在创建它的范围内的可用的变量。系统提供的dispatch方法为了方便地使用GCD,苹果提供了一些方法方便我们将block放在主线程 或 后台线程执行,或者延后执行。使用的例子如下:// 后台执行: dispatch_async(dispatch_get_global_queue(0, 0), ^{ // something }); // 主线程执行: dispatch_async(dispatch_get_main_queue(), ^{ // something }); // 一次性执行: static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ // code to be executed once }); // 延迟2秒执行: double delayInSeconds = 2.0; dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, delayInSeconds * NSEC_PER_SEC); dispatch_after(popTime, dispatch_get_main_queue(), ^(void){ // code to be executed on the main queue after delay });// dispatch_queue_t 也可以自己定义,如要要自定义queue,可以用dispatch_queue_create方法,示例如下:dispatch_queue_t urls_queue = dispatch_queue_create("blog.devtang.com", NULL);dispatch_async(urls_queue, ^{ // your code });dispatch_release(urls_queue);后台运行GCD的另一个用处是可以让程序在后台较长久的运行。在没有使用GCD时,当app被按home键退出后,app仅有最多5秒钟的时候做一些保存或清理资源的工作。但是在使用GCD后,app最多有10分钟的时间在后台长久运行。这个时间可以用来做清理本地缓存,发送统计数据等工作。另外,GCD还有一些高级用法,例如让后台2个线程并行执行,然后等2个线程都结束后,再汇总执行结果。这个可以用dispatch_group, dispatch_group_async 和 dispatch_group_notify来实现,示例如下:dispatch_group_t group = dispatch_group_create();dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ // 并行执行的线程一 }); dispatch_group_async(group, dispatch_get_global_queue(0,0), ^{ // 并行执行的线程二 }); dispatch_group_notify(group, dispatch_get_global_queue(0,0), ^{ // 汇总结果 });让程序在后台长久运行的示例代码如下:// AppDelegate.h文件 @property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundUpdateTask;// AppDelegate.m文件 - (void)applicationDidEnterBackground:(UIApplication *)application{ [self beingBackgroundUpdateTask]; // 在这里加上你需要长久运行的代码 [self endBackgroundUpdateTask];}- (void)beingBackgroundUpdateTask{ self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }];}- (void)endBackgroundUpdateTask{ [[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask]; self.backgroundUpdateTask = UIBackgroundTaskInvalid;}NSOperationQueue与GCD的对比对于NSOperationQueue和GCD应该用哪个,一般来说可以用编程界比较通用的原则来决定:Always use the highest-level abstraction available to you, and drop down to lower-level abstractions when measurement shows that they are needed.简意是:尽可能用更高级抽象的方法。但前面提到的StackOverflow里的讨论里却分别说出了两者的优缺点:NSOperation好处:很容易设置两个NSOperation之间的依赖来让某一个操作在上一个操作完成后才执行方便设置在同一时间运行的操作个数您可以创建操作,支持在第一时间被取消bandwidth-constrained queues that only run N operations at a timeestablishing dependencies between operationsyou can create operations that support being cancelled in the first placeGCD好处:NSOperation对象在创建或释放过程中会消耗明显的CPU资源使用Blocks后代码比使用NSOperation更简洁The NSOperation object allocation and deallocation process took a significant amount of CPU resources when dealing with small and frequent actions, like rendering an OpenGL ES frame to the screen. GCD blocks completely eliminated that overhead, leading to significant performance improvements.code is cleaner when using blocks than NSOperations.当然,具体使用哪一个还是要看你的使用场合了。
0 0
原创粉丝点击