您的位置:首页 > 移动开发 > IOS开发

iOS多线程开发

2015-06-22 22:56 381 查看

进程和线程的基本知识

进程:在面向多线程设计的系统中(如当代多数操作系统,当然也包括iOS,Mac OS X),一个运行的程序就是一个进程。每个进程是由私有的虚拟地址空间、代码、数据和其它各种系统资源组成,进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。进程本身不是一个执行单位,而是线程的容器。

线程:线程是进程内部的一个执行单元。系统创建好进程后,实际上就启动执行了该进程的主线程,主线程以函数地址形式,比如说main或WinMain函数,将程序的启动点提供给操作系统。主线程终止了,进程也就随之终止。每个线程只有一些运行必备的资源,调用栈(call stack),寄存器环境(register context),线程本地存储(thread-local storage)。多个线程共享所属进程的系统资源。

  每一个进程至少有一个主线程,它无需由用户去主动创建,是由系统自动创建的。用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。多线程可以实现并行处理,避免了某项任务长时间占用CPU时间。在单核CPU的机器中,为了运行所有这些线程,操作系统为每个独立线程安排一些CPU时间,操作系统以轮换方式向线程提供时间片,这就给人一种假象,好象这些线程都在同时运行。

多线程的应用:我们可以把耗时的数据计算和IO操作放在子线程上执行,保证主线程响应用户操作。当数据计算/IO操作执行完再通知主线程做UI的更新。这样就大大的提高了资源利用率。

注意:多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的。

多线程的问题:

1、如果两个非常活跃的线程为了抢夺对CPU的控制权,在线程切换时会消耗很多的CPU资源,反而会降低系统的性能。

2、在iOS系统中,主线程堆栈大小为1M,子线程堆栈大小为512K。如果创建过多的线程,会造成大量的内存和CPU时间的消耗。

3、因为线程不拥有系统资源,多个线程共享进程的资源,在两个以上线程同时操作相同资源时,容易造成死锁或者制造出垃圾数据。

所以在你想要使用多线程进行编程的时候,一定要想清楚是否必要,切勿滥用。

iOS多线程编程

在iOS开发中,有三种方式多线程技术:NSThread,NSOperation,GCD。这三种技术随着Mac/iOS发展,一步步引入的。下面我们逐一进行分析。并总结出每种技术的使用环境。

[b]NSThread[/b]

NSThread是轻量级线程类,需要我们自己管理线程的生命周期。

创建线程的几种方式:

显示创建

+ (void)detachNewThreadSelector:(SEL)selector toTarget:(id)target withObject:(id)argument;


通过类方法创建一个线程,并自动启动

- (instancetype)initWithTarget:(id)target selector:(SEL)selector object:(id)argument


通过对象方法创建一个线程,需要手动调用线程的start方法来启动

隐式创建

- (void)performSelectorInBackground:(SEL)aSelector withObject:(id)arg


这个是NSObject类目(category)方法,APPLE为所有继承NSObject的类添加了这个方法。这个方法隐式地创建了一个后台线程来执行aSelector。同样不需要手动调用start来启动

线程通信

通常我们启动新线程是用来执行耗时的操作,以避免阻塞主线程,当执行完这些耗时操作后,通知主线程来做某些显示操作。

比如,现在我们在要从网络中获取图片并显示在View上的一个需求。我们就可以启动一个线程来帮我们完成下载图片的操作,当下载完成后,再通知主线程显示图片。

通知主线程的方法

- (void)performSelectorOnMainThread:(SEL)aSelector withObject:(id)arg waitUntilDone:(BOOL)wait


还有一种是,一个线程通知另一个线程执行某操作,这种情况很少,不做介绍。方法如下

- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait


优点:

1、轻量级,当执行简单的多线程操作的时候,我们可以使用NSThread。经常使用的NSObject封装的线程方法

缺点:

1、需要我们自己管理线程生命周期

2、不能实现线程的依赖,比如我们启用两个线程1、2,需要等待线程1完成后再执行线程2的操作。这个时候,我们用NSThread就很没办法实现了

3、在前面我们知道启动一个线程需要占用内存(主线程1M,子线程512K),当我们启用线程过多,势必会占用大量的内存,得不偿失

已知上面的缺点,引出下面我们要讨论的另一种多线程方式NSOperation

[b]NSOperation[/b]

NSOperation晚于NSThread出现,增添许多NSThread没有的特性。比如NSOperation可以设置最大线程数,可以设置线程依赖等等。

NSOperation是抽象基类(需要注意的是,OC和其他如C++,C#等语言不可生成抽象类对象不同。我们仍然可以生成NSOperation的对象),通常我们使用NSOperation的子类:NSInvocationOperation,NSBlockOperation,这两个子类本质上没有区别,只是NSBlockOperation通过block方式来组织代码,代码结构相对清晰。当我们定义好需要的操作的时候,需要生成一个操作队列:NSOperationQueue,把我们前面生成的NSInvocationOperation或NSBlockOperation添加到队列中,就可以执行,我们不需要手动启用线程。NSOperationQueue就是一个线程池,具体线程池哪个线程来执行这个操作,我们不需要关系,只要把操作添加进去就行了。

假如我们有一个需求。我们要下载两张照片1、2,照片1必须在照片2下载完,才执行下载照片1的操作。

我们可以声明两个操作1、2,让操作1依赖操作2。便可以实现上面的需求。

为了避免文章太长,我们就把上面所说到的知识点全用上,写了以下代码。里面有注释,不难,大家肯定可以看得懂😄

- (void)viewDidLoad {
[super viewDidLoad];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.maxConcurrentOperationCount = 2; // 最大线程数为2

// 生成NSInvocationOperation
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(downloadDataFromNet) object:nil];
[queue addOperation:operation]; // 把operation添加到执行队列queue中

// 生成NSBlockOperation
NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(@"%s:%@", __FUNCTION__, [NSThread currentThread]);
}];
[operation addDependency:blockOperation]; // operation依赖blockOperation,当blockOperation执行完才会执行operation,这样我们就强制操作的执行顺序
[queue addOperation:blockOperation]; // 把blockOperation添加到执行队列queue中
}

- (void)downloadDataFromNet{
//    NSLog(@"%d", [NSThread isMultiThreaded]);
NSLog(@"%s:%@", __FUNCTION__, [NSThread currentThread]);
}


如果APPLE提供的NSInvocationOperation,NSBlockOperation不能满足你,你可以自定义一个操作MyOperation,继承NSOperation。我们需要重写NSOperation的方法main等方法,在这先不做介绍。

优点:

1、隐藏了复杂的线程生命周期的管理

2、可以明确线程依赖关系

3、可以设置最大线程数

4、可以取消任务执行

[b]GCD[/b]

[b]待续。。。。。。[/b]

  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: