指引网

当前位置: 主页 > 编程开发 > C >

C语言中多线程NSThread/NSOperation/GCD的使用

来源:网络 作者:佚名 点击: 时间:2017-07-19 23:08
[摘要]  多线程能够在同一时间执行多于一个线程,进而提升整体处理性能,本文我们分别讲解多线程中NSThread,NSOperation,GCD的使用的

多线程NSThread的使用

NSThread每个NSThread对象对应一个线程,轻量级。
NSThread:优点:NSThread比其他俩个轻量级,使用简单。
                 缺点:需要自己管理线程的生命周期、线程同步、加锁、睡眠以及唤醒等。线程同步对数据的加锁会有一定的系统开销。
 
NSThread的几种创建方式

//方式一:利用perform开启多线程,并且执行方法threadAction
//    [self performSelectorInBackground:@selector(threadAction) withObject:@"thread"];
    
    //方式二:
//    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadAction:) object:@"text"];
//    thread.name = @"thread1";
//    //开启线程
//    [thread start];
    
    //方式三:开启新的线程,并且执行
    [NSThread detachNewThreadSelector:@selector(threadAction:) toTarget:self withObject:@"thread2"];

对比主线程与多线程在执行上的先后顺序:
在viewDidLoad里写一个for循环。

for (int i=0; i<50; i++) {
    NSLog(@"主线程:%d",i);
}

在多线程的threadAction:方法里也同样写一个for循环

for (int i=0; i<50; i++) {
    NSLog(@"多线程:%d",i);
}

打印结果:

 
通过俩次打印结果,我们知道他们是没有先后顺序的,而且每次打印都不同。
有几个常用的方法我们可能会用到:


//获取当前线程
NSThread *thread = [NSThread currentThread];
//判断当前是否在多线程
[NSThread isMultiThreaded]
//判断当前是否在主线程
[NSThread isMainThread]
//让当前线程睡眠几秒
[NSThread sleepForTimeInterval:3];
//回到主线程
//    [self performSelectorOnMainThread:<#(SEL)#> withObject:<#(id)#> waitUntilDone:<#(BOOL)#>]


思考:我们为什么要使用多线程?
 
总结: 提高CPU的利用率,让程序更加流畅。如果我们把所有的任务都放在主线程会造成主线程阻塞。比如说当你在加载较多的图片时,你的textView是不能滚动的。下面我们就针对这方面写一个demo.
 
首先我们给UIimageView添加一个类目,用来让其能够加载网络图片


类目的.h文件

#import <UIKit/UIKit.h>
@interface UIImageView (cache)
//为UIImageView写一个添加网络图片的方法
- (void)setimage:(NSString *)str;
@end



类目的.m文件

#import "UIImageView+cache.h"
@implementation UIImageView (cache)
//如果这样直接写方法,在主线程,当我们在加载网络时不能滑动TextView
- (void)setimage:(NSString *)str
{
    NSURL *url = [NSURL URLWithString:str];
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    self.image = [UIImage imageWithData:data];
}
@end



类目写好了,让我们用起来吧。
viewController里的代码如下:

#import "ViewController.h"
#import "UIImageView+cache.h"
@interface ViewController ()
{
    UIImageView *_image;
    
    NSMutableArray *arr;
}
- (IBAction)click:(UIButton *)sender;
@end
@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    
    arr = [NSMutableArray array];
    
    //创建八行六列的UIImageView
    for (int i = 0; i < 6; i ++) {
        for (int j = 0 ; j < 8; j ++) {
            
            _image = [[UIImageView alloc]initWithFrame:CGRectMake(i * 62, j * 62 , 60, 60)];
            
            _image.backgroundColor = [UIColor yellowColor];
            
            [self.view addSubview:_image];
            //将创建好的UIImageView放进可变数组。
            [arr addObject:_image];
        }
    }
}
- (IBAction)click:(UIButton *)sender {
    
    for (UIImageView *imageview in arr) {
        //利用分类给数组里的UIImageView添加图片
        [imageview setimage:@"http://img31.mtime.cn/pi/2013/03/08/144644.81111130_1280X720.jpg"];
        
    }
    
}
@end



这样的运行结果是:


那么为了解决这样阻塞主线程的情况
我们把分类的方法该为:

- (void)setimage:(NSString *)str
{
    //开启一个多线程,并且把str通过创建多线程传递到多线程的任务中(注:这里的字符串为网络图片的地址)
    [NSThread detachNewThreadSelector:@selector(thredAction:) toTarget:self withObject:str];
}
//多线程的任务
- (void)thredAction:(NSString *)str
{
    //将字符串转换成URL
    NSURL *url = [NSURL URLWithString:str];
    //将url转化成data
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    //注意:UI的修改只能放在主线程 所以写在这里还是错误
    //self.image = [UIImage imageWithData:data];
    
    //回到主线程
    [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:YES];
    
}


这样就可以解决线程阻塞的问题了。



多线程NSOperation的使用

NSOperation/NSOperationQueue面向对象的线程技术。

 
NSOperation:不需要关心线程管理,数据同步的事情,可以把精力放在自己需要执行的操作上。NSOpertion是面向对象的。
 
NSOperation的创建以及常用的方法:

//创建线程队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //关闭暂停队列,用于让所有的线程加入队列后在执行任务
    [queue setSuspended:YES];
    
    //设置最大并发数,
    queue.maxConcurrentOperationCount = 2;
    
    //创建线程
    NSInvocationOperation *operation1 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation1:)  object:@"operation1"];
    
    //设置线程优先级
    operation1.queuePriority = NSOperationQueuePriorityNormal;
    
    //创建第二个线程
    NSInvocationOperation *operation2 = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operation2:) object:@"operation2"];
    //设置线程优先级
    operation2.queuePriority = NSOperationQueuePriorityVeryHigh;
    
    //将线程加入队列
    [queue addOperation:operation1];
    [queue addOperation:operation2];
    
    
    //用Block创建多线程
    
    [queue addOperationWithBlock:^{
    
      //注意这里我们要将任务放进自动释放池,因为这段代码不在(主线程)中
        @autoreleasepool {
            
            for (int i = 0; i < 10; i ++) {
                
                NSLog(@"operation3%d",i);
            }
        }
    }];
    
    
    //开始队列
    [queue setSuspended:NO];


说完创建方式及常用方法,属性。也来说说其解决主线程阻塞的问题
例子跟上篇文章大致是一样的。
类目中的主要代码:

#import "UIImageView+cache.h"
@implementation UIImageView (cache)
- (void)setImages:(NSString *)str
{
    //创建队列
    NSOperationQueue *queue = [[NSOperationQueue alloc]init];
    
    //设置最大并发
    queue.maxConcurrentOperationCount = 1;
    //创建线程
    NSInvocationOperation *operation = [[NSInvocationOperation alloc]initWithTarget:self selector:@selector(operationAction:) object:str];
    
    //将线程加入线程队列
    [queue addOperation:operation];
    
    
}
- (void)operationAction:(NSString *)str
{
    NSURL *url = [NSURL URLWithString:str];
    
    NSData *data = [NSData dataWithContentsOfURL:url];
    
    //在这里我们要修改UI了所以要重新回到主线程
    //第一种回主线程的方式
    [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:YES];
    /*
     //第二种方式回到主线程
     
     [[NSOperationQueue mainQueue] addOperationWithBlock:^{
     
     self.image = [UIImage imageWithData:data];
     
     }];
     */
    
}
@end
viewController中的代码:
#import "ViewController.h"
#import "UIImageView+cache.h"
@interface ViewController ()
@property (nonatomic, strong)NSMutableArray *dataArr;
@end
@implementation ViewController
- (NSMutableArray *)dataArr
{
    if (_dataArr == nil) {
        _dataArr = [NSMutableArray array];
        
        return _dataArr;
    }
    
    return _dataArr;
}
- (void)viewDidLoad {
    [super viewDidLoad];
    
    
    for (int i = 0; i < 8; i ++) {
        for (int j = 0; j < 6; j ++) {
            
            UIImageView *imageview = [[UIImageView alloc]initWithFrame:CGRectMake(j * 64, i * 80, 60, 75)];
            imageview.backgroundColor = [UIColor yellowColor];
            
            [self.view addSubview:imageview];
            
            [self.dataArr addObject:imageview];
            
        }
    }
    
}
- (IBAction)click:(UIButton *)sender {
    
    for (UIImageView *imageview in self.dataArr) {
        
        [imageview setImages:@"http://img31.mtime.cn/pi/2013/03/08/144644.81111130_1280X720.jpg"];
        
    }
}
@end


这样就解决了主线程阻塞的问题。



多线程GCD的使用

GCD——Grand Central Dispatch 是基于C语言的框架,可以充分利用多核,也是苹果官方推荐使用的多线程技术。

 
         GCD是由苹果开发的一个多核编程的解决方案。iOS4.0+才能使用,是替代NSThread,NSOperation的高效和强大的技术,GCD是基于C语言的

         GCD是苹果公司为多核的并行运算提出的解决方案

         GCD会自动利用更多的CPU内核(比如双核、四核)

         GCD会自动管理线程的生命周期(创建线程、调度任务、销毁线程)
程序员只需要告诉GCD想要执行什么任务,不需要编写任何线程管理代码
 
多线程里面概念性的东西很多也很难记,所以一定要好好理解。还是通过代码,在温习温习吧。相同的代码写多次,虽然有时很枯燥,可每次都会有不同的体会和领悟。走起!
viewController中:

#import "ViewController.h"
@interface ViewController ()
@end
@implementation ViewController
/*
//写之前,再来温习温习。
 GCD的使用:
 一 :队列
 1.串行队列:添加到队列中的任务是一个一个执行的
 2.并发(行)队列:添加到队列中的任务是多个同时执行的
 3.主队列:里面的任务都是在主线程执行的。
 4.全局队列:并行(发)队列
 
 二:同步、异步
 1、同步:需要后面的任务等待,不会开启新的线程,会直接使用当前的线程
 
 2、异步:不需要后面的任务等待,会开启新的线程
 */

- (void)viewDidLoad {
    [super viewDidLoad];
    
    /*---------创建并发队列---------*/

    //1.创建并行队列
    dispatch_queue_t queue1 = dispatch_queue_create("queue1", DISPATCH_QUEUE_CONCURRENT);
    
    //2.获取全局队列(并行队列)
    /*优先级<#long identifier#>
     DISPATCH_QUEUE_PRIORITY_HIGH
     DISPATCH_QUEUE_PRIORITY_DEFAULT
     DISPATCH_QUEUE_PRIORITY_LOW
     DISPATCH_QUEUE_PRIORITY_BACKGROUND
     */
    
    dispatch_queue_t queue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
    
    //(1)在并发队列添加异步任务

    dispatch_async(queue2, ^{
    
                  for (int i = 0; i < 50 ;i ++) {
                
                NSLog(@"1---------1");
            }
        
        
    });
    
    dispatch_async(queue2, ^{
        
        
            for (int i = 0; i < 50 ;i ++) {
                
                NSLog(@"2---------2");
            }
        
        
    });

打印结果:

 
通过打印结果我们可以看出:
(1)并发队列异步添加任务会开多条线程,执行顺序是没有先后(同时执行)
 
看完并发队列的异步添加,让我们再来看看并发队列的同步添加:

dispatch_sync(queue2, ^{
    
    @autoreleasepool {
        for (int i = 0; i < 50 ;i ++) {
            
            NSLog(@"1---------1");
        }
    }
});
dispatch_sync(queue2, ^{
    
    @autoreleasepool {
        for (int i = 0; i < 50 ;i ++) {
            
            NSLog(@"2---------2");
        }
    }
});


这个打印结果相信不用说,我们都知道肯定是先执行1  在执行2



其实执行问题好回答,关键是此时的任务是在那个线程完成的,相信如果你对同步添加的概念如果十分清楚的话一定能回答对,没有错。这些任务是在主线程中完成的。并不是在queue2中完成的。让我们看图吧,有图有真相


通过以上分析我们可以看出:
(1)在并发队列中同步添加任务,不会开启新的线程,会直接使用当前线程,
(2)并且,我们添加的任务是添加到主线程。并不是表面现象的queue2。
 
 
看完并发(行)队列,让我们来看看串行队列吧。

/*---------创建串行----------*/
    
dispatch_queue_t queue = dispatch_queue_create("queue3", DISPATCH_QUEUE_SERIAL);
//向串行队列异步添加任务
dispatch_async(queue, ^{
    for (int i=0; i<50; i++) {
        NSLog(@"🐷....i:%d",i);
    }
});
dispatch_async(queue, ^{
    for (int i=0; i<50; i++) {
        NSLog(@"🐶....i:%d",i);
    }
});


打印结果:


尽管是异步添加任务,但是该队列是串行队列,所以猪 先被执行,然后才执行狗。

如果此时我们再在viewDidLoad中也就是在狗的for训话下面添加一个for循环的话你能想象出结果吗?

for (int i=0; i<100; i++) {
        NSLog(@"🐱....i:%d",i);
 }

打印结果:


不难看出猫与(狗和猪)是同时执行的。而(猪和狗)则是猪先执行,狗在执行。例如猪和狗排了一个队,而猫自己排了一个队。
 
继续介绍:

向串行队列同步添加任务

dispatch_sync(queue, ^{
     
    for (int i=0; i<50; i++) {
        NSLog(@"🐶....i:%d",i);
    }
});
 
dispatch_sync(queue, ^{
 
    for (int i=0; i<100; i++) {
        NSLog(@"🐱....i:%d",i);
    }
     
});
 
for (int i=0; i<100; i++) {
    NSLog(@"🐷....i:%d",i);
}


 运行结果毫无疑问。狗-----》猫----------》猪;时刻记着同步添加任务,不会开启新的线程,会直接使用当前线程。那么肯定这三个任务也是在当前的主线程。

 

介绍完这些,还有一个重要的东西那就是死锁

让我们来看一下

获取主队列:

    dispatch_queue_t mainQueue = dispatch_get_main_queue();

分配任务:

NSLog(@"任务开始啦!!!");
    //同步添加任务
    dispatch_sync(mainQueue, ^{
    
        NSLog(@"任务添加");
        
    });
    
    NSLog(@"结束");


毫无疑问第一个打印肯定是  第一行的“任务开始啦!!!”,那么接下来走第三行代码,因为是Block所以第五行的代码要在第九行的打印执行结束后才能被执行(block回调),又因为是同步添加任务,所以第九行代码要在上面的任务执行完才能开始。所以编译器犹豫不知道该执行那一个。会造成死锁。

打印结果:



总结:(1)在使用主队列时,绝对不可以在主队列中同步添加任务,会造成死锁。

         (2)并发队列:同步添加不会开启新的线程,异步添加会开启多条线程。

         (3)串行队列:同步添加不会开启新的线程,异步添加会开启一条线程。

         (4)由(2)和(3)得出结论同步添加任务,不具备开启线程的能力。异步添加任务开启线程的条数由当前队列决定,串行队列开启一条线程,并行队列开启多条。

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