程序员人生 网站导航

IOS 多线程原理

栏目:综合技术时间:2015-04-13 08:15:42

iPhone中的线程利用其实不是无控制的,官方给出的资料显示iPhone OS下的主线程的堆栈大小是1M,第2个线程开始都是512KB。并且该值不能通过编译器开关或线程API函数来更改。只有主线程有直接修改UI的能力。

1.线程概述

有些程序是1条直线,出发点到终点;有些程序是1个圆,不断循环,直到将它切断。直线的如简单的Hello World,运行打印完,它的生命周期便结束了,像昙花1现那样;圆如操作系统,1直运行直到你关机。 
1个运行着的程序就是1个进程或叫做1个任务,1个进程最少包括1个线程,线程就是程序的履行流。Mac和iOS中的程序启动,创建好1个进程的同时, 1个线程便开始运行,这个线程叫主线程。主线程在程序中的地位和其他线程不同,它是其他线程终究的父线程,且所有界面的显示操作即AppKit或 UIKit的操作必须在主线程进行。 
系统中的每个进程都有自己独立的虚拟内存空间,而同1个进程中的多个线程则共用进程的内存空间。每创建1个新的线程,都需要1些内存(如每一个线程有自己的Stack空间)和消耗1定的CPU时间。另外当多个线程对同1个资源出现争取的时候需要注意线程安全问题。

 

2.创建线程

创建1个新的线程就是给进程增加了1个履行流,履行流总得有要履行的代码吧,所以新建1个线程需要提供1个函数或方法作为线程的入口。

1.使用NSThread

NSThread提供了创建线程的途径,还可以提供了检测当前线程是不是是主线程的方法。 使用NSThread创建1个新的线程有两种方式:

  • 1.创建1个NSThread的对象,调用其start方法。对这类方式的NSThread对象的创建,可使用1个目标对象的方法初始化1个NSThread对象,或创建1个继承NSThread类的子类,实现其main方法,然后在直接创建这个子类的对象。
  • 2.使用 detachNewThreadSelector:toTarget:withObject:这个类方法创建1个线程,这个比较直接了,直接使用目标对象的方法作为线程启动入口。

2.使用NSObject

其实NSObject直接就加入了多线程的支持,允许对象的某个方法在后台运行。如:

  1. [myObj performSelectorInBackground:@selector(doSomething) withObject:nil]; 

3.POSIX Thread

由于Mac和iOS都是基于Darwin系统,Darwin系统的XUN内核,是基于Mach和BSD的,继承了BSD的POSIX接口,所以可以直接使用POSIX线程的相干接口来使用线程。

创建线程的接口为 pthread_create,固然在创建之前可以通过相干函数设置好线程的属性。以下为POSIX线程使用简单的例子。

  1. // //  main.c //  pthread // //  Created by Lu Kejin on 1/27/12. //  Copyright (c) 2012 Taobao.com. Al 

 

3.多线程进阶

NSOperation&NSOperationQueue

很多时候我们使用多线程,需要控制线程的并发数,毕竟线程也是消耗系统资源的,当程序中同时运行的线程过量时,系统必定变慢。 所以很多时候我们会控制同时运行线程的数目。

NSOperation可以封装我们的操作,然后将创建好的NSOperation对象放到NSOperationQueue中,OperationQueue便开始启动新的线程去履行队列中的操作,OperationQueue的并发度是可以通过以下方式进行设置:

  1. - (void)setMaxConcurrentOperationCount:(NSInteger)count 

GCD

GCD是Grand Central Dispatch的缩写,是1系列的BSD层面的接口,在Mac 10.6 和iOS4.0以后才引入的,且现在NSOperation和NSOperationQueue的多线程的实现就是基于GCD的。目前这个特性也被移植到 FreeBSD上了,可以查看libdispatch这个开源项目。

比如1个在UIImageView中显示1个比较大的图片

  1. dispatch_queue_t imageDownloadQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(imageDownloa 

固然,GCD除处理多线程外还有很多非常好的功能,其建立在强大的kqueue之上,效力也能够得到保障。

 

4.线程间通讯

线程间通讯和进程间通讯从本质上讲是相似的。线程间通讯就是在进程内的两个履行流之间进行数据的传递,就像两条并行的河流之间挖出了1道单向活动长沟,使得1条河流中的水可以流入另外一条河流,物资得到了传递。

1.performSelect On The Thread 

框架为我们提供了强迫在某个线程中履行方法的途径,如果两个非主线程的线程需要相互间通讯,可以先将自己确当前线程对象注册到某个全局的对象中去,这样相 互之间就能够获得对方的线程对象,然后就能够使用下面的方法进行线程间的通讯了,由于主线程比较特殊,所以框架直接提供了在出线程履行的方法。

  1. @interface NSObject (NSThreadPerformAdditions) - (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUnti 

       2.Mach Port 
在苹果的Thread Programming Guide的Run Pool1节的Configuring a Port-Based Input Source 这1段中就有使用Mach Port进行线程间通讯的例子。 其实质就是父线程创建1个NSMachPort对象,在创建子线程的时候以参数的方式将其传递给子线程,这模样线程中就能够向这个传过来的 NSMachPort对象发送消息,如果想让父线程也能够向子线程发消息的话,那末子线程可以先向父线程发个特殊的消息,传过来的是自己创建的另外一个 NSMachPort对象,这样父线程便持有了子线程创建的port对象了,可以向这个子线程的port对象发送消息了。

固然各自的port对象需要设置delegate和schdule到自己所在线程的RunLoop中,这样来了消息以后,处理port消息的delegate方法会被调用,你就能够自己处理消息了。

 

5.RunLoop

RunLoop从字面上看是运行循环的意思,这1点也不错,它确切就是1个循环的概念,或准确的说是线程中的循环。 本文1开始就提到有些程序是1个圈,这个圈本质上就是这里的所谓的RunLoop,就是1个循环,只是这个循环里加入很多特性。 
首先循环体的开始需要检测是不是有需要处理的事件,如果有则去处理,如果没有则进入眠眠以节省CPU时间。 所以重点便是这个需要处理的事件,在RunLoop中,需要处理的事件分两类,1种是输入源,1种是定时器,定时器好理解就是那些需要定时履行的操作,输 入源分3类:performSelector源,基于端口(Mach port)的源,和自定义的源。编程的时候可以添加自己的源。RunLoop还有1个视察者Observer的概念,可以往RunLoop中加入自己的 视察者以便监控着RunLoop的运行进程,CFRunLoop.h中定义了所有视察者的类型:

  1. enum CFRunLoopActivity { kCFRunLoopEntry = (1 << 0), kCFRunLoopBeforeTimers = (1 << 1), kCFRunLoopBeforeSources = ( 

如果你使用过select系统调用写进程序你即可以快速的理解runloop事件源的概念,本质上讲事件源的机制和select1样是1种多路复用IO的 实现,在1个线程中我们需要做的事情其实不单1,如需要处理定时钟事件,需要处理用户的触控事件,需要接受网络远端发过来的数据,将这些需要做的事情统统注 册到事件源中,每次循环的开始便去检查这些事件源是不是有需要处理的数据,有的话则去处理。 拿具体的利用举个例子,NSURLConnection网络数据要求,默许是异步的方式,其实现原理就是创建以后将其作为事件源加入到当前的 RunLoop,而等待网络响应和网络数据接受的进程则在1个新创建的独立的线程中完成,当这个线程处理到某个阶段的时候比如得到对方的响应或接受完 了网络数据以后便通知之前的线程去履行其相干的delegate方法。所以在Cocoa中常常看到scheduleInRunLoop:forMode: 这样的方法,这个便是将其加入到事件源中,当检测到某个事件产生的时候,相干的delegate方法便被调用。对CoreFoundation这1层而 言,通常的模式是创建输入源,然后将输入源通过CFRunLoopAddSource函数加入到RunLoop中,相干事件产生后,相干的回调函数会被调 用。如CFSocket的使用。 另外RunLoop中还有1个运行模式的概念,每个运行循环必定运行在某个模式下,而模式的存在是为了过滤事件源和视察者的,只有那些和当前 RunLoop运行模式1致的事件源和视察者才会被激活。

每个线程都有其对应的RunLoop,但是默许非主线程的RunLoop是没有运行的,需要为RunLoop添加最少1个事件源,然后去run它。1般情况下我们是没有必要去启用线程的RunLoop的,除非你在1个单独的线程中需要久长的检测某个事件。


------分隔线----------------------------
------分隔线----------------------------

最新技术推荐