ios基础篇(二十九)—— 多线程(Thread、Cocoa operations和GCD)

1、进程与线程

一、进程编程

进程是指在系统中正在运行的一个应用程序,每一个进程之间是独立的,每一个进程均运行在其专用且受保护的内存空间内;网络

若是咱们把CPU比做一个工厂,那么进程就比如工厂的车间,一个工厂有好多个车间,每一个车间都在进行不一样的工做,它们之间是独立互不干扰的。多线程

二、线程并发

线程是进程的基本执行单元,一个进程的全部任务都在线程中执行;一个进程要想执行任务,必须得有线程(每一个进程至少要有1条线程);app

线程就比如车间里的工人,一个车间里能够有好多工人(一个进程能够包括多个线程),他们协同完成一个任务;异步

2、多线程

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

一个进程中能够开启多条线程,每条线程能够并行(同时)执行不一样的任务,能够提升程序的执行效率;函数

原理:同一时间,CPU只能处理1条线程,只有1条线程在工做(执行);多线程并发(同时)执行,实际上是CPU快速地在多条线程之间调度(切换)若是CPU调度线程的时间足够快,就形成了多线程并发执行的假象。oop

优缺点:
性能

 多线程的优势:

   (1)能适当提升程序的执行效率

   (2)能适当提升资源利用率(CPU、内存利用率)

多线程的缺点:

       (1)开启线程须要占用必定的内存空间(默认状况下,主线程占用1M,子线程占用512KB),若是开启大量的线程,会占用大量的内存空间,下降程序的性能

   线程越多,CPU在调度线程上的开销就越大

   (2)程序设计更加复杂:好比线程之间的通讯、多线程的数据共享

 

 iOS中几种多线程实现:

一、Thread

二、Cocoa operations

三、GCD(Grand Central Dispatch)(iOS4 以后)

 

一、NSThread

(1)NSThread有两种建立方式:

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

 + (void)detachNewThreadSelector:(SEL)aSelector toTarget:(id)aTarget withObject:(id)anArgument;

    //实例方法
    thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:nil];
//启动线程 [thread start];
//类方法 [NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:nil];

selector :线程执行的方法,selector只能有一个参数,且不能有返回值;

target  :selector消息发送的对象;

object :传输给target的惟一参数,也能够是nil;

 两种建立方式的不一样:

类方法一调用就会当即建立一个线程来作事情;

实例方法要直到咱们手动调用 start 启动线程时才会真正去建立线程;

(2)不显式建立线程的方法(间接建立):

利用NSObject的方法 performSelectorInBackground:withObject:来建立;

//隐含产生新线程
[myView performSelectorInBackground:@selector(Action:) withObject:nil];

(3)NSThread相关属性及方法:

@property (copy) NSString *name;  // 获取/设置线程的名字

+ (NSThread *)currentThread;  // 获取当前线程的线程对象

+ (void)sleepForTimeInterval:(NSTimeInterval)ti;  // 线程休眠(秒)

+ (void)sleepUntilDate:(NSDate *)date;  // 线程休眠,指定具体什么时间休眠

+ (void)exit;  // 退出线程线程对象销毁,销毁后就不能再次启动线程,不然程序会崩溃)

 

二、NSOperation

(1)NSInvocationOperation

NSInvocationOperation的建立:

 NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationAction) object:nil];//object能够带一个参数
//启动线程,默认是不启动
 [operation start];

   - (void)operationAction{}

参数和NSTread同样。

(2)NSBlockOperation

NSBlockOperation 是 NSOperation 类的另一个系统预约义的子类,咱们能够用它来封装一个或多个 block。

通常来讲,有如下两个场景咱们会优先使用 NSBlockOperation 类: 

  • 当咱们在应用中已经使用了 Operation Queues 且不想建立 Dispatch Queues 时,NSBlockOperation 类能够为咱们的应用提供一个面向对象的封装;

  • 咱们须要用到 Dispatch Queues 不具有的功能时,好比须要设置 operation 之间的依赖关系、使用 KVO 观察 operation 的状态变化等;

NSBlockOperation的建立:

咱们可使它并发执行,经过使用addExecutionBlock方法添加多个Block,这样就能使它在主线程和其它子线程中工做。

- (NSBlockOperation*)blockOperation{

    NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"block1,mainThread:%@,currebtThread:%@",[NSThread mainThread],[NSThread currentThread]);
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block2,mainThread:%@,currebtThread:%@",[NSThread mainThread],[NSThread currentThread]);
        NSLog(@"Finish block2");
    }];
    [blockOperation addExecutionBlock:^{
        NSLog(@"block3,mainThread:%@,currebtThread:%@",[NSThread mainThread],[NSThread currentThread]);
        NSLog(@"Finish block3");
    }];
    
    return blockOperation;
}

 

(3)NSOperationQueue

一个NSOperation对象能够经过调用start方法来执行任务,默认是同步执行的;也能够将NSOperation添加到一个NSOperationQueue(操做队列)中去执行,并且是异步执行的。

一、NSOperation方法及属性:

// 设置线程的最大并发数
@property NSInteger maxConcurrentOperationCount;

// 线程完成后调用的Block
@property (copy) void (^completionBlock)(void);

// 取消线程
- (void)cancel;

二、建立一个操做队列:

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

三、添加NSOperation到NSOperationQueue中:

    //添加一个operation
   [operationQueue addOperation:operation];
    
    //添加一组operation
    NSArray *operations = [NSArray arrayWithObjects:@"",@"", nil];
    [operationQueue addOperations:operations waitUntilFinished:NO];
    
    //添加一个block形式的operation
    [operationQueue addOperationWithBlock:^() {
        NSLog(@"blockOperation:%@", [NSThread currentThread]);
    }];

四、设置NSOperation的依赖对象

(1)当某个NSOperation对象依赖于其它NSOperation对象的完成时,就能够经过addDependency方法添加一个或者多个依赖的对象,只有全部依赖的对象都已经完成操做,当前NSOperation对象才会开始执行操做。经过removeDependency方法来删除依赖对象。

以下代码:operation1依赖operation2,意思为先执行operation2,operation2完成后继续执行operation1;

[operation2 addDependency:operation1];

删除依赖对象,删除后则不存在依赖关系;以下代码:

[operation2 removeDependency:operation1]; 

(2)没有设置依赖关系的状况下:(默认)

    NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第1次操做:%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第2次操做:%@", [NSThread currentThread]);
    }];
    
    [operationQueue addOperation:operation1];
    [operationQueue addOperation:operation2];

打印:

由打印信息能够看出,默认是按顺序进行的;

(3)设置依赖关系的状况下:

NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
    
    NSBlockOperation *operation1 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第1次操做:%@", [NSThread currentThread]);
    }];
    
    NSBlockOperation *operation2 = [NSBlockOperation blockOperationWithBlock:^(){
        NSLog(@"执行第2次操做:%@", [NSThread currentThread]);
    }];
    
    [operation1 addDependency:operation2];
    
    [operationQueue addOperation:operation1];
    [operationQueue addOperation:operation2];

打印:

能够看出程序先执行operation2,后执行operation1。

若是要解除依赖关系,则:

[operation1 removeDependency:operation2];

注意:在NSOperationQueue类中,咱们可使用cancelAllOperations方法取消全部的线程。这里须要注意一下,不是执行cancelAllOperations方法时就会立刻取消,是等当前队列执行完,下面的队列不会再执行。

 

三、 GCD(Grand Central Dispatch)

GCD  是Apple公司开发的一种技术,异步执行任务的技术之一,它旨在优化多核环境中的并发操做并取代传统多线程的编程模式;在Mac OS X 10.6和IOS 4.0以后开始支持GCD。

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

GCD中的FIFO(First In First Out)队列称为dispatch queue,它能够保证先进来的任务先获得执行;

Dispatch Queue分三种:

(1)Main queue:main dispatch queue 是一个全局可用的串行队列,在应用程序的主线程上执行任务。此队列的任务和应用程序的主循环(run loop)要执行的事件源交替执行。由于运行在应用程序的主线程,main queue常常用来做为应用程序的一个同步点。

(2)Serial quque: 又称private dispatch queue(私有调度队列),每次运行一个任务,能够添加多个,执行次序FIFO,通常用再对特定资源的同步访问上。咱们能够根据须要建立任意数量的串行队列,每个串行队列之间是并发的。

(3)Concurrent queue: 又称为global dispatch queue,能够并发地执行多个任务,可是执行完成的顺序是随机的.

 

使用方法:

(1)dispatch_async

dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

async代表异步运行,除了async,还有sync(同步),delay(延时);

block表明的是你要作的事情;

queue则是你把任务交给谁来处理了;

dispatch_async 这个函数是异步的,这就意味着它会当即返回而无论block是否运行结束。所以,咱们能够在block里运行各类耗时的操做(如网络请求) 而同时不会阻塞UI线程。

//默认优先级的Global Dispatch Queue中执行Block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
     //可并行执行的处理
     //在Main Dispatch Queue中执行Block
     dispatch_async(dispatch_get_main_queue(), ^{
          //只能在主线程中执行的处理
          });
     });

举个例子看看它的实际用法:(下载一张图片)

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    imageView = [[UIImageView alloc] initWithFrame:self.view.bounds];
    [self.view addSubview:imageView];

  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        NSURL *url = [NSURL URLWithString:@"http://e.hiphotos.baidu.com/image/h%3D200/sign=5dafb2a3586034a836e2bf81fb1249d9/d31b0ef41bd5ad6e194e5f4885cb39dbb7fd3cd8.jpg"];
        NSData *data = [NSData dataWithContentsOfURL:url];
        UIImage *image = [[UIImage alloc] initWithData:data];
        if (data) {
            dispatch_async(dispatch_get_main_queue(), ^{
                imageView.image = image;
            });
        }
    });
}

运行:

比起NSThread和NSOperation用法是否是简单多了。

系统给每个应用程序提供了四个concurrent dispatch queues,这四个并发调度队列是全局的,它们只有优先级的不一样;

//Global Dispatch Queue 默认优先级的获取方法
dispatch_queue_t globalDispatchQueueDefault = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//Global Dispatch Queue高优先级的获取方法
dispatch_queue_t globalDispatchQueueHigh = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//Global Dispatch Queue 低优先级的获取方法
dispatch_queue_t globalDispatchQueueLow = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//Global Dispatch Queue 后台优先级的获取方法
dispatch_queue_t globalDispatchQueueBackground = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
 
由于是全局的,咱们不须要去建立。咱们只须要经过使用函数dispath_get_global_queue去获得队列;
dispatch_queue_t globalQ = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);   
dispatch_queue_t mainQ = dispatch_get_main_queue();   
 
2)dispatch_group_async
dispatch_group_async能够实现监听一组任务是否完成,完成后获得通知执行其余的操做。这个方法颇有用,好比你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。
 
监视Dispatch Queue处理执行的结束。
dispatch_group_create();
dispatch_group_async();
dispatch_group_notify();
dispatch_group_wait();
 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, ^{
        [NSThread sleepForTimeInterval:1];
        NSLog(@"group1");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"group2");
    });
    dispatch_group_async(group, queue, ^{
        [NSThread sleepForTimeInterval:3];
        NSLog(@"group3"); 
    });
  dispatch_group_notify(group, dispatch_get_main_queue(), ^{ 
      NSLog(@"print"); 
  });

打印:

(3)dispatch_barrier_async

dispatch_barrier_async是在前面的任务执行结束后它才执行,并且它后面的任务等它执行完成以后才会执行;

dispatch_queue_t queue = dispatch_queue_create("1", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"queue1");
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:4];
        NSLog(@"queue2");
    });
    dispatch_barrier_async(queue, ^{
        NSLog(@"dispatch_barrier_async");
        [NSThread sleepForTimeInterval:4];
        
    });
    dispatch_async(queue, ^{
        [NSThread sleepForTimeInterval:2];
        NSLog(@"queue3");
    });

打印:

 

(4)dispatch_apply

执行某个代码片断几回。
dispatch_apply(2, globalQ, ^(size_t index) {
    // 执行2次
});

(5)dispatch_suspend/dispatch_resume

当追加大量处理到Dispatch Queue时, 在追加处理的过程当中,有时但愿不执行已追加的处理。  此时,咱们须要挂起Dispatch Queue。当能够执行时再恢复。
suspend是挂起,resume是恢复。
 
(6)dispatch_once
保证应用程序执行中只执行一次指定处理的API。
 
(7)Dispatch I/O
一次使用多线程更快地并列读取文件。
经过Dispatch I/O读写文件时,使用Global Dispatch Queue将一个文件按某个大小read/write。
也能够将文件分割为一块一块地进行读取处理,分割读取的数据经过使用Dispatch Data能够更为简单地进行结合和分割 。
 dispatch_io_create  生成Dispatch IO, 指定发生错误时用来执行处理的Block,以及执行该Block的Dispatch Queue。
 dispatch_io_set_low_water函数 设定一次读取的大小(分割的大小),
 dispatch_io_read函数使用Global Dispatch Queue开始并列读取。
相关文章
相关标签/搜索