多线程、特别是NSOperation 和 GCD 的内部原理

来源:互联网 发布:opengl 纹理变形算法 编辑:程序博客网 时间:2024/04/30 20:34

多线程、特别是NSOperation 和 GCD 的内部原理

简介

多线程是一个比较轻量级的方法来实现单个应用程序内多个代码执行路径。

在系统级别内,程序并排执行,程序分配到每个程序的执行时间是基于该程序的所需时间和其他程序的所需时间来决定的。

然而,在每个程序内部,存在一个或者多个执行线程,它同时或在一个几乎同时发生的方式里执行不同的任务。

概要提示:

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

一、线程概述

有些程序是一条直线,起点到终点——如简单的hello world,运行打印完,它的生命周期便结束了,像是昙花一现。

有些程序是一个圆,不断循环直到将它切断——如操作系统,一直运行直到你关机。

一个运行着的程序就是一个进程或者叫做一个任务,一个进程至少包含一个线程,线程就是程序的执行流。

Mac和iOS中的程序启动,创建好一个进程的同时,一个线程便开始运作,这个线程叫做主线程。主线成在程序中的位置和其他线程不同,它是其他线程最终的父线程,且所有的界面的显示操作即AppKit或UIKit的操作必须在主线程进行。

系统中每一个进程都有自己独立的虚拟内存空间,而同一个进程中的多个线程则公用进程的内存空间。

每创建一个新的进成,都需要一些内存(如每个线程有自己的stack空间)和消耗一定的CPU时间。

当多个进成对同一个资源出现争夺的时候需要注意线程安全问题

iOS有三种多线程编程的技术,分别是:


(一)NSThread 


(二)Cocoa NSOperation 


(三)GCD(全称:Grand Central Dispatch) 

三种方式的优缺点介绍:

1)NSThread

优点:NSThread 比其他两个轻量级

缺点:需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销

2)Cocoa NSOperation

优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。

Cocoa operation相关的类是NSOperation, NSOperationQueue.

NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类: NSInvocationOperation和NSBlockOperation.

创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

3) GCD(全优点)

Grand Central dispatch(GCD)是Apple开发的一个多核编程的解决方案。在iOS4.0开始之后才能使用。GCD是一个替代NSThread, NSOperationQueue,NSInvocationOperation等技术的很高效强大的技术。

下面我用简单易于理解的代码实例来介绍NSThread在开发实践中的使用,具体使用时可以根据实际情况进行扩展:

一、NSThread的使用(基本已过时)

1

import 

define kURL @”http://www.iyi8.com/uploadfile/2014/0506/20140506085929652.jpg”


@interface ViewController : UIViewController 


@end 

1

import “ViewController.h”


@interface ViewController () 

4 { 


UIImage *_image; 


UIImageView *_imageView; 

7


int _tickets; 


int _count; 

10

11 
NSThread *_threadOne; 

12 
NSThread *_threadTwo; 

13 
NSThread *_threadThree; 

14

15 
NSCondition *_condition; 

16

17 
NSLock *_lock; 

18 

19 
@end 

20

21 
@implementation ViewController 

22

23 
- (id)initWithNibName:(NSString )nibNameOrNil bundle:(NSBundle )nibBundleOrNil 

24 

25 
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; 

26 
if (self) { 

27 

28 
return self; 

29 


- (void)loadView 

2 { 


self.view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 480)]; 


NSLog(@”klfaklfa —— - - “); 

5


_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(100, 100, 100, 150)]; 


[self.view addSubview:_imageView]; 

8 } 

/* 测试NSthread使用/


- (void)viewDidLoad 

2 { 


[super viewDidLoad]; 

4


_tickets = 200; 


_count = 0; 


_lock = [[NSLock alloc] init]; 

8


//锁对象 

10 
_condition = [[NSCondition alloc] init]; 

11 
//线程1 

12 
_threadOne = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; 

13 
_threadOne.name = @”thread-1”; 

14 
[_threadOne start]; 

15

16 
//线程2 

17 
_threadTwo = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil]; 

18 
_threadTwo.name = @”thread-2”; 

19 
[_threadTwo start]; 

20

21 
//线程3 

22 
_threadThree = [[NSThread alloc] initWithTarget:self selector:@selector(run3) object:nil]; 

23 
_threadThree.name = @”thread-3”; 

24 
[_threadThree start]; 

25

26 
//如果没有线程同步的lock,物品售出数量就会出现重复导致数据竞争不同步问题.加上lock之后线程同步保证了数据的正确性。 

27 
[NSThread detachNewThreadSelector:@selector(downloadImage:) toTarget:self withObject:kURL]; 

28 
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(downloadImage:) object:kURL]; 

29 
[thread start]; 

30 

/** 测试NSOperationQueue使用

使用 NSOperation的方式有两种,

一种是用定义好的两个子类:NSInvocationOperation 和 NSBlockOperation。

另一种是继承NSOperation

如果你也熟悉Java,NSOperation就和java.lang.Runnable接口很相似。和Java的Runnable一样,NSOperation也是设计用来扩展的,只需继承重写NSOperation的一个方法main。相当与java 中Runnalbe的Run方法。然后把NSOperation子类的对象放入NSOperationQueue队列中,该队列就会启动并开始处理它。

NSInvocationOperation例子:

这里同样,我们实现一个下载图片的例子。新建一个Single View app,拖放一个ImageView控件到xib界面。

实现代码如下

*/


- (void)viewDidLoad 

2 { 


[super viewDidLoad]; 

4


NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 


selector:@selector(downloadImage:) 


object:kURL]; 

8


NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 

10 
[queue addOperation:operation]; 

11 

第二种方式继承NSOperation 

在.m文件中实现main方法,main方法编写要执行的代码即可。

如何控制线程池中的线程数?

队列里可以加入很多个NSOperation, 可以把NSOperationQueue看作一个线程池,可往线程池中添加操作(NSOperation)到队列中。线程池中的线程可看作消费者,从队列中取走操作,并执行它。

通过下面的代码设置:

1 [queue setMaxConcurrentOperationCount:5]; 

线程池中的线程数,也就是并发操作数。默认情况下是-1,-1表示没有限制,这样会同时运行队列中的全部的操作。

/* 测试GCD的dispatch_async使用/

[html] view plaincopy 


- (void)viewDidLoad 

2 { 


[super viewDidLoad]; 

4


dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ 


NSURL * url = [NSURL URLWithString:kURL]; 


NSData * data = [[NSData alloc]initWithContentsOfURL:url]; 


UIImage *image = [[UIImage alloc]initWithData:data]; 

9

10 
if (data != nil) { 

11 
dispatch_async(dispatch_get_main_queue(), ^{ 

12 
_imageView.image = image; 

13 
}); 

14 

15 
}); 

16 

(三)GCD的介绍和使用

介绍:

Grand Central Dispatch 简称(GCD)是苹果公司开发的技术,以优化的应用程序支持多核心处理器和其他的对称多处理系统的系统。这建立在任务并行执行的线程池模式的基础上的。它首次发布在Mac OS X 10.6 ,iOS 4及以上也可用。

设计:

GCD的工作原理是:让程序平行排队的特定任务,根据可用的处理资源,安排他们在任何可用的处理器核心上执行任务。

一个任务可以是一个函数(function)或者是一个block。 GCD的底层依然是用线程实现,不过这样可以让程序员不用关注实现的细节。

GCD中的FIFO队列称为dispatch queue,它可以保证先进来的任务先得到执行。

dispatch queue分为下面三种:

Serial 

又称为private dispatch queues,同时只执行一个任务。Serial queue通常用于同步访问特定的资源或数据。当你创建多个Serial queue时,虽然它们各自是同步执行的,但Serial queue与Serial queue之间是并发执行的。

Concurrent 

又称为global dispatch queue,可以并发地执行多个任务,但是执行完成的顺序是随机的。

Main dispatch queue 

它是全局可用的serial queue,它是在应用程序主线程上执行任务的。

我们看看dispatch queue如何使用?

1、常用的方法dispatch_async

为了避免界面在处理耗时的操作时卡死,比如读取网络数据,IO,数据库读写等,我们会在另外一个线程中处理这些操作,然后通知主线程更新界面。

用GCD实现这个流程的操作比前面介绍的NSThread NSOperation的方法都要简单。代码框架结构如下

/* 测试GCD的dispatch_group_async使用/

[html] view plaincopy 


- (void)viewDidLoad 

2 { 


[super viewDidLoad]; 

4


//此方法可以实现监听一组任务是否完成,如果完成后通知其他操作(如界面更新),此方法在下载附件时挺有用, 


//在搪行几个下载任务时,当下载完成后通过dispatch_group_notify通知主线程下载完成并更新相应界面 


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 


dispatch_group_t group = dispatch_group_create(); 


dispatch_group_async(group, queue, ^{ 

10 
[NSThread sleepForTimeInterval:0.09]; 

11

12 
NSLog(@”group1”); 

13 
NSURL * url = [NSURL URLWithString:kURL]; 

14 
NSData * data = [[NSData alloc]initWithContentsOfURL:url]; 

15 
_image = [[UIImage alloc]initWithData:data]; 

16

17 
}); 

18 
dispatch_group_async(group, queue, ^{ 

19 
[NSThread sleepForTimeInterval:0.09]; 

20 
NSLog(@”group2”); 

21 
}); 

22 
dispatch_group_async(group, queue, ^{ 

23 
[NSThread sleepForTimeInterval:0.09]; 

24 
NSLog(@”group3”); 

25 
}); 

26

27 
dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 

28 
NSLog(@”updateUi”); 

29

30 
_imageView.image = _image; 

31 
}); 

32 

/* 测试GCD的dispatch_barrier_async使用/

[html] view plaincopy 


- (void)viewDidLoad 

2 { 


[super viewDidLoad]; 

4


//是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行 


dispatch_queue_t queue = dispatch_queue_create(“gcd.devdiy.com”, DISPATCH_QUEUE_CONCURRENT); 


dispatch_async(queue, ^{ 


[NSThread sleepForTimeInterval:2]; 


NSLog(@”dispatch_async1”); 

10 
}); 

11

12 
dispatch_async(queue, ^{ 

13 
[NSThread sleepForTimeInterval:4]; 

14 
NSLog(@”dispatch_async2”); 

15 
}); 

16

17 
dispatch_barrier_async(queue, ^{ 

18 
NSLog(@”dispatch_barrier_async”); 

19 
[NSThread sleepForTimeInterval:4]; 

20 
}); 

21

22 
dispatch_async(queue, ^{ 

23 
[NSThread sleepForTimeInterval:1]; 

24 
NSLog(@”dispatch_async”); 

25 
}); 

26 

[html] view plaincopy 


- (void)run3 

2 { 


while (true) { 


[_condition lock]; 


[NSThread sleepForTimeInterval:3]; 


NSLog(@”当前物品名称:%d,售出数量:%d,线程名-=-==: %@”, _tickets, _count, [[NSThread currentThread] name]); 

7


[_condition signal]; 


[_condition unlock]; 

10 

11 

12

13 
- (void)run 

14 

15 
while (true) { 

16 
//上锁 

17 
[_lock lock]; 

18 
if (_tickets > 0) { 

19 
[NSThread sleepForTimeInterval:0.09]; 

20 
_count = 200 - _tickets; 

21 
NSLog(@”当前物品名称:%d,售出数量:%d,线程名: %@”, _tickets, _count, [[NSThread currentThread] name]); 

22 
_tickets–; 

23 
}else{ 

24 
break; 

25 

26

27 
[_lock unlock]; 

28 

29 

30

31 
/** 

32 
* @param string url 

33 
*/ 

34 
- (void)downloadImage:(NSString *) url{ 

35 
NSData *data = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]]; 

36 
UIImage *image = [[UIImage alloc] initWithData:data]; 

37 
if(image == nil){ 

38 
[self updateUI:image]; 

39 
}else{ 

40 
[self performSelectorOnMainThread:@selector(updateUI:) withObject:image waitUntilDone:YES]; 

41 

42 

43

44 
- (void)updateUI:(UIImage *)image 

45 

46 
_imageView.image = image; 

47 

48

49 
@end 

是不是代码比NSThread NSOperation简洁很多,而且GCD会自动根据任务在多核处理器上分配资源,优化程序。

系统给每一个应用程序提供了三个concurrent dispatch queues。这三个并发调度队列是全局的,它们只有优先级的不同。因为是全局的,我们不需要去创建。我们只需要通过使用函数dispath_get_global_queue去得到队列。

0 0
原创粉丝点击