iOS之多线程开发(NSThread,NSOperation,GCD)

今天闲来无事,就来总结一下iOS中的多线程开发吧。


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

1.NSThread------每个NSThread对象对应一个线程,量级较轻(真正的多线程)

2.NSOperation------NSOperation/NSOperationQueue 面向对象的线程技术

3.GCD------Grand Central Dispatch(派发) 是基于C语言的框架,可以充分利用多核,是苹果推荐使用的多线程技术

其中,NSOperation和GCD是苹果专门开发的“并发”技术,使得程序员可以不再去关心线程的具体使用问题。

以上这三种编程方式从上到下,抽象度层次是从低到高的,抽象度越高的使用越简单,也是Apple最推荐使用的,在项目中很多框架技术分别使用了不同多线程技术。


三种多线程技术的对比

1.NSThread
优点:NSThread 比其他两个轻量级
缺点:需要自己管理线程的生命周期、线程同步、睡眠以及唤醒。线程同步对数据的加锁会有一定的系统开销

2.Cocoa  NSOperation
优点:不需要关心线程管理, 数据同步的事情,可以把精力放在自己需要执行的操作上。
Cocoa operation相关的类是NSOperation, NSOperationQueue.
NSOperation是个抽象类,使用它必须用它的子类,可以实现它或者使用它定义好的两个子类: NSInvocationOperation和NSBlockOperation.创建NSOperation子类的对象,把对象添加到NSOperationQueue队列里执行。

--NSOperation是面向对象的

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

--GCD是基于C语言的,执行效率高


三种多线程技术的实现

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

1.创建使用线程:

类方法直接开启后台线程,并执行选择器方法

// 新建一个线程,调用@selector方法
[NSThread detachNewThreadSelector:@selector(bigDemo) toTarget:self withObject:nil];
成员方法,在实例化线程对象之后,需要使用start执行选择器方法
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(bigDemo) object:nil];
// 启动start线程
[thread start];
对于NSThread的简单使用,可以用NSObject的performSelectorInBackground替代
// performSelectorInBackground是将bigDemo的任务放在后台线程中执行
[self performSelectorInBackground:@selector(bigDemo) withObject:nil];

或者

//在指定线程中执行,但该线程必须具备run loop。
- (void)performSelector:(SEL)aSelector onThread:(NSThread *)thr withObject:(id)arg waitUntilDone:(BOOL)wait; 

2.线程中代码执行完成后,需要回调主线程才能更新UI

//第一种方式
//[self performSelectorOnMainThread:@selector(settingImage:) withObject:image waitUntilDone:NO];
//第二种方式
//[self.imageView performSelector:@selector(setImage:) onThread:[NSThread mainThread] withObject:image waitUntilDone:NO];
//第三种方式
[self.iconView performSelectorOnMainThread:@selector(setImage:) withObject:image waitUntilDone:NO];
3.其它一些方法
+ (NSThread *)currentThread; //获得当前线程
+ (void)sleepForTimeInterval:(NSTimeInterval)ti; //线程休眠
+ (NSThread *)mainThread; //主线程,亦即UI线程了
- (BOOL)isMainThread; + (BOOL)isMainThread; //当前线程是否主线程
- (BOOL)isExecuting; //线程是否正在运行
- (BOOL)isFinished; //线程是否已结束
4.线程之间的同步(多个线程访问同一资源时)

涉及到线程间同步仍然需要配合使用NSLock,NSCondition 或者 @synchronized

例子一(NSLock)

-(void)run{  
    [self.lock lock];//给lock锁上,这里可以通俗地理解为一间房一次只能进一个人,每个进去时把门反锁,出来时把门打开  
    if(self.count > 0){  
        self.count--;  
        NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);  
    }  
    [self.lock unlock];//解锁  
}

例子二(@synchronized)

@synchronized(self){  
    if(self.count > 0){  
       self.count--;  
        NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);  
    }  
} 

例子三(NSCondition)

[_condition lock]; 
if(self.count > 0){  
       self.count--;  
        NSLog(@"%@抢到一张火车票", [[NSThread currentThread] name]);  
    } 
[_condition signal];  
[_condition unlock];


二、NSOperation,面向对象的多线程技

使用步骤:

1.实例化操作

// 实例化操作队列
_queue = [[NSOperationQueue alloc] init];
2.NSInvocationOperation例子
NSInvocationOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(opAction) object:nil];
// 如果使用start,会在当前线程启动操作
// [op1 start];
// 一旦将操作添加到操作队列,操作就会启动
[_queue addOperation:op1];
3.NSBlockOperation例子
- (IBAction)operationDemo3:(id)sender
{
     //下载
     NSBlockOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"下载 %@" , [NSThread currentThread]);
     }];
     //滤镜
     NSBlockOperation *op2 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"滤镜 %@" , [NSThread currentThread]);
     }];
     //显示
     NSBlockOperation *op3 = [NSBlockOperation blockOperationWithBlock:^{
         NSLog(@"更新UI %@" , [NSThread currentThread]);
     }];
     
     // 添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动
     // 依赖关系可以跨线程队列实现
     // 提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
     [op2 addDependency:op1];
     [op3 addDependency:op2];
     //[op1 addDependency:op3];
     
     [_queue addOperation:op1];
     [_queue addOperation:op2];
     [[NSOperationQueue mainQueue] addOperation:op3];
}

4.其它控制介绍

a.将操作添加到队列NSOperationQueue即可启动多线程执行

[_queue addOperation:op1];
[_queue addOperation:op2];
b.更新UI使用主线程队列
//两方式
[NSOpeationQueue mainQueue] addOperation ^{
};
[[NSOperationQueue mainQueue] addOperation:op3];
c.操作队列的setMaxConcurrentOperationCount,可以设置同时并发的线程数量!
[_queue setMaxConcurrentOperationCount:2];
提示:此功能仅有NSOperation有!
d.使用addDependency可以设置任务的执行先后顺序,同时可以跨操作队列指定依赖关系
// 添加操作之间的依赖关系,所谓“依赖”关系,就是等待前一个任务完成后,后一个任务才能启动
// 依赖关系可以跨线程队列实现
// 提示:在指定依赖关系时,注意不要循环依赖,否则不工作。
[op2 addDependency:op1];
[op3 addDependency:op2];
[op1 addDependency:op3];
提示:在指定依赖关系时,注意不要循环依赖,否则不工作。


三、GCD(C语言)

GCD就是为了在“多核”上使用多线程技术,要使用GCD,所有的方法都是dispatch开头的

名词解释:
global全局
queue队列
async异步---把代码块提交给队列后,立即返回
sync同步---把代码块提交给队列后要等代码块执行完才返回,阻塞当前线程

1.关于GCD的队列

a.全局队列(global)--队列内部并行无序(+async异步 +sync同步)

参数:优先级 DISPATCH_QUEUE_PRIORITY_DEFAULT 始终是 0

dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

b.串行队列(Serial)--队列内部串行

dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_SERIAL);

是创建得到的,不能直接获取,当你创建多个Serial queue时,虽然它们各自内部是串行执行的,但Serial queue与Serial queue之间是并发执行的。

c.并行队列(Concurrent)--队列内部并行FIFO(+async异步 +sync同步)

dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT)

d.主队列(Main)--它是全局可用的serial queue,它是在应用程序主线程上执行任务的,只能同步执行

dispatch_async(dispatch_get_main_queue(), ^{
        NSLog(@"main - > %@", [NSThread currentThread]);
});

2.GCD简单应用例子

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{  
     NSURL * url = [NSURL URLWithString:<span>kURL</span>];  
     NSData * data = [[NSData alloc]initWithContentsOfURL:url];  
     UIImage *image = [[UIImage alloc]initWithData:data];  
          
         if (data != nil) {  
             dispatch_async(dispatch_get_main_queue(), ^{  
             _imageView.image = image;  
            });  
         }  
     });

3.GCD的队列应用

a.创建串行队列提交同步任务

dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        //code 任务一
    });
    dispatch_sync(queue, ^{
        //code 任务二
    });
队列中的任务是串行出列的,任务一执行结束后执行任务二,然而sync又是同步任务,会阻塞当前线程,实际上这里造成了死锁。
b.自定义串行队列,提交异步任务
dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_SERIAL);   
    dispatch_async(queue, ^{
        //code 任务一
    });
    dispatch_async(queue, ^{
        //code 任务二
    });
队列的任务是串行出列,任务一执行结束后执行任务二。

c.自定义并行队列,提交同步任务

dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
    dispatch_sync(queue, ^{
        //code 任务一
    });
    dispatch_sync(queue, ^{
        //code 任务二
    });
队列的任务是并行出列,顺序按先进先出的顺序执行,既任务一出列后任务二接着出列(但任务二与任务一又是同步的,一般不这么用)
d.自定义并行队列,提交异步任务

dispatch_queue_t queue = dispatch_queue_create("queueName", DISPATCH_QUEUE_CONCURRENT);
    dispatch_async(queue, ^{
        //code 任务一
    });
    dispatch_async(queue, ^{
        //code 任务一
    });
任务一出列后任务二出列,各任务之间是异步的,不会阻塞当前线程。

e.在主队列提交同步任务,阻塞主线程,造成死锁!

dispatch_sync(dispatch_get_main_queue(), ^{
        //code
    });
f.在主队列提交异步任务
dispatch_async(dispatch_get_main_queue(), ^{
        //code任务
    });
g.在全局队列提交同步任务
dispatch_sync(dispatch_get_global_queue(0, 0), ^{
     //code
 });
h.在全局队列提交异步任务
dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //code
    });
dispatch_get_gloabal_queue 的第一个参数为枚举类型(默认为0),决定任务的优先级 ,第二个参数为Apple保留参数,传0

i.总结

队列的类型决定了队列任务的执行方式(主队列是一个串行队列)。一般把会阻塞主线程的任务提交到异步并行队列当中,防止死锁形成。

4.GCD的dispatch_group_async使用

实现监听一组任务是否完成,完成后得到通知执行其他的操作。这个方法很有用,比如你执行三个下载任务,当三个任务都下载完成后你才通知界面说完成的了。

//此方法可以实现监听一组任务是否完成,如果完成后通知其他操作(如界面更新),此方法在下载附件时挺有用,  
     //在搪行几个下载任务时,当下载完成后通过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, ^{  
         [NSThread sleepForTimeInterval:0.09];  
       
     NSLog(@"group1");  
     NSURL * url = [NSURL URLWithString:kURL];  
     NSData * data = [[NSData alloc]initWithContentsOfURL:url];  
     _image = [[UIImage alloc]initWithData:data];  
       
     });  
     dispatch_group_async(group, queue, ^{  
         [NSThread sleepForTimeInterval:0.09];  
         NSLog(@"group2");  
     });  
     dispatch_group_async(group, queue, ^{  
         [NSThread sleepForTimeInterval:0.09];  
         NSLog(@"group3");  
     });  
       
     dispatch_group_notify(group, dispatch_get_main_queue(), ^{  
         NSLog(@"updateUi");  
       
         _imageView.image = _image;  
     });
5.GCD的dispatch_barrier_async使用
- (void)viewDidLoad  
{  
    [super viewDidLoad];  
      
    //是在前面的任务执行结束后它才执行,而且它后面的任务等它执行完成之后才会执行  
    dispatch_queue_t queue = dispatch_queue_create("gcd.devdiy.com", DISPATCH_QUEUE_CONCURRENT);  
    dispatch_async(queue, ^{  
        [NSThread sleepForTimeInterval:2];  
        NSLog(@"dispatch_async1");  
    });  
      
    dispatch_async(queue, ^{  
        [NSThread sleepForTimeInterval:4];  
        NSLog(@"dispatch_async2");  
    });  
      
    dispatch_barrier_async(queue, ^{  
        NSLog(@"dispatch_barrier_async");  
        [NSThread sleepForTimeInterval:4];  
    });  
      
    dispatch_async(queue, ^{  
        [NSThread sleepForTimeInterval:1];  
        NSLog(@"dispatch_async");  
    });  
}   


线程与进程的区别

进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性。
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。
进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。
线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉。
所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。
但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。






  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值