iOS之多线程

1、多线程简介:用户使用APP的时候如果每个操作都会等待好长的时间,才能得到程序的响应,这就影响了用户体验,为了减少用户的等待时间,我们可以使用多线程:开辟出子线程去处理一些比较耗时的操作(例如加载数据),这样减少了用户的等待时间提升了用户体验,但是开辟线程会占用一定的内存,(主线程所占内存大小是1M,子线程所占内存大小都是512KB,并且该值不能通过编译器开关或线程API函数来更改)降低程序的性能。所以一般情况下不要同时开辟过多线程。

2、使用多线程可能会出现的几个问题:多个线程同时访问共享的数据发生Critical Section、Race Condition;一个进程中多个线程之间的来回切换Context Switch,产生额外开销;为防止多线程同时访问时争抢造成的数据安全问题,需要使用互斥锁(但是需要消耗大量的资源);两个(多个)线程都要等待对方完成某个操作才能进行下一步,这时就会发生死锁。

多线程的实现有多种方式:NSThread、NSObject、NSOperation和GCD,其中较常用的是GCD,首先是几种多线程之间优缺点比较(前人总结直接粘过来的)

  1. NSThread (抽象层次:低)
    • 优点:轻量级,简单易用,可以直接操作线程对象
    • 缺点: 需要自己管理线程的生命周期,线程同步。线程同步对数据的加锁会有一定的系统开销。
  2.  NSOperation (抽象层次:中)
    • 优点:不需要关心线程管理,数据同步的事情,可以把精力放在学要执行的操作上。基于GCD,是对GCD 的封装,比GCD更加面向对象
    • 缺点: NSOperation是个抽象类,使用它必须使用它的子类,可以实现它或者使用它定义好的两个子类NSInvocationOperation、NSBlockOperation.
  3. GCD 全称Grand Center Dispatch (抽象层次:高)

    • 优点:是 Apple 开发的一个多核编程的解决方法,简单易用,效率高,速度快,基于C语言,更底层更高效,并且不是Cocoa框架的一部分,自动管理线程生命周期(创建线程、调度任务、销毁线程)。
    • 缺点: 使用GCD的场景如果很复杂,就有非常大的可能遇到死锁问题。

下面分别介绍一下基础操作:

首先NSThread开辟子线程:

NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(sayHi) object:nil];//手动开启子线程

[thread start];

[NSThread exit];//使用NSThread和NSObject实现的开辟线程,系统会自动释放,关不关都行

NSThread自动开辟子线程:[NSThread detachNewThreadSelector:@selector(sayHi) toTarget:self withObject:nil];

#pragma mark - NSObject 开启子线程

[self performSelectorInBackground:@selector(sayHi) withObject:@"test"];//开辟子线程:sayHi方法在子线程中实现

[self performSelectorOnMainThread:@selector(mainThreadChangeColor) withObject:nil waitUntilDone:YES];//返回到主线程

#pragma mark - NSOperation——————NSOperation不能直接进行多线程的创建,需要借助:NSOperationQueue

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(test) object:nil];//使用NSOperarion的第一个子类去创建多线程:NSInvocationOperation

NSBlockOperation *blockOperation = [NSBlockOperation blockOperationWithBlock:^{
        NSLog(@"我是子线程");
    }];//使用NSOperation 的第二个子类创建子线程(这里可以只使用其中一个子类):若单独使用(不使用NSOperationQueue,只开辟一个子线程)需要添加开启方法:[blockOperation start];

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:operation];
    [queue addOperation:blockOperation];

#pragma mark - GCD

dispatch_queue_t queue = dispatch_queue_create(DISPATCH_QUEUE_SERIAL, 0);//开辟子线程:串行队列

dispatch_queue_t queue = dispatch_queue_create(0, DISPATCH_QUEUE_CONCURRENT);//开辟子线程:并行队列,串行与并行任选一个

//添加任务(若有多个任务时)//创建串行队列:DISPATCH_QUEUE_SERIAL串行:任务顺序执行;DISPATCH_QUEUE_CONCURRENT并行:任务没有次序

dispatch_async(queue, ^{
 NSLog(@"currentThread1 == %@, mainThread1 == %@, ismain == %d", [NSThread currentThread], [NSThread mainThread], [NSThread isMainThread]);//这里的部分将会在子线程下实现

dispatch_async(dispatch_get_main_queue(), ^{

//返回主线程。这里的部分将会在主线程下实现。一般数据的解析加载在子线程中实现,UI的刷新在主线程中实现,最后会举了简单的小例子说明一下
         });
 });

#++++++解析数据并刷新UI

    //url
    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
    //创建请求
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    //创建session
    NSURLSession *session = [NSURLSession sharedSession];
    //创建task
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error == nil) {
        //处理数据;这里是使用异步block自动创建的子线程,在这里处理数据,防止主线程出现卡顿
            
            //回到主线程刷新UI
            dispatch_async(dispatch_get_main_queue(), ^{
                
                
            });
            
        }
    }];
NSOperationQueue GCD的比较:

NSOperationQueue使用addOperation方法将NSOperation类型的对象加入队列,并且该NSOperation类型的对象是NSOperation的子类创建的对象(NSInvocationOperation和NSBlockOperation);使用NSOperationQueue时不能出现start方法;
 GCD:使用dispatch_queue_create方法创建子线程:根据该方法内的参数的不同从而创建串行队列(DISPATCH_QUEUE_SERIAL)和并行队列(DISPATCH_QUEUE_CONCURRENT);
      使用dispatch_async添加任务;

CGD的应用比较广泛,在这里重点介绍一下:

常用的主线程串行队列异步执行任务,在主线程运行,不会产生死锁。

 

1 dispatch_queue_t mainQueue = dispatch_get_main_queue();
2 dispatch_async(mainQueue,^{
3   NSLog(@"ismain == %d", [NSThread isMainThread]);//isman == 1            
4 });//只是在返回主线程的时候用,这里并不是创建子线程

1   dispatch_queue_t mainQueue = dispatch_queue_create(0, DISPATCH_QUEUE_SERIAL);
2 dispatch_async(mainQueue,^{ 
3 NSLog(@"ismain == %d", [NSThread isMainThread]);//isman == 0;注意与上边代码的区别 //创建了子线程
4 });

Global queue(全局并发队列)

//程序默认的队列级别(队列执行的优先级),DISPATCH_QUEUE_PRIORITY_DEFAULT == 0
dispatch_queue_t globalQueue1 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//HIGH
dispatch_queue_t globalQueue2 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0);
//LOW
dispatch_queue_t globalQueue3 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
//BACKGROUND
dispatch_queue_t globalQueue4 = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
 

 

1 dispatch_queue_t globalQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);3 dispatch_sync(globalQueue, ^{//isman == 1;"sync"同步执行,未开辟子线程(可以简单理解为同步执行==未开辟子线程;异步执行==开辟子线程(特例除外))
4        sleep(2.0);//同步执行会出现卡顿,将“sync”改为“async”,isman == 0开辟了子线程
NSLog(@"ismain == %d", [NSThread isMainThread]);//当有多个任务时,异步执行无顺序;同步执行,先加入先执行
6 });

Custom queue (自定义串/并行队列)

dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);//若想定义并行队列将“DISPATCH_QUEUE_SERIAL”改为DISPATCH_QUEUE_CONCURRENT

dispatch_async(serialQueue, ^{
NSLog(@"ismain == %d", [NSThread isMainThread]);
});

注意:注意不要嵌套使用同步执行的串行队列任务

1 dispatch_queue_t serialQueue = dispatch_queue_create("com.dullgrass.serialQueue", DISPATCH_QUEUE_SERIAL);
2 dispatch_sync(serialQueue, ^{   //无论该行是同步执行还是异步执行,红色代码部分都不会执行,
3   NSLog(@"会执行的代码");
4   dispatch_sync(serialQueue, ^{
5       NSLog(@"代码不执行");
6   });
7 });

dispatch_after延时添加到队列:

 1 dispatch_time_t delayTime3 = dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC);
 2 dispatch_time_t delayTime2 = dispatch_time(DISPATCH_TIME_NOW, 2*NSEC_PER_SEC);
 3 dispatch_queue_t mainQueue = dispatch_get_main_queue();
 4 NSLog(@"current task");
 5 dispatch_after(delayTime3, mainQueue, ^{
 6       NSLog(@"3秒之后添加到队列");
 7 });
 8 dispatch_after(delayTime2, mainQueue, ^{
 9        NSLog(@"2秒之后添加到队列");
10 });
11 NSLog(@"next task");

 

dispatch_apply在给定的队列上多次执行某一任务,在主线程直接调用会阻塞主线程去执行block中的任务。

  • dispatch_apply函数的功能:把一项任务提交到队列中多次执行,队列可以是串行也可以是并行,dispatch_apply不会立刻返回,在执行完block中的任务后才会返回,是同步执行的函数。
  • dispatch_apply正确使用方法:为了不阻塞主线程,一般把dispatch_apply放在异步队列中调用,然后执行完成后通知主线程
 1 dispatch_queue_t globalQueue = dispatch_get_global_queue(0, 0);
 2 NSLog(@"current task");
 3 dispatch_async(globalQueue, ^{
 4    dispatch_queue_t applyQueue = dispatch_get_global_queue(0, 0);
 5    //第一个参数,3--block执行的次数
 6    //第二个参数,applyQueue--block任务提交到的队列
 7    //第三个参数,block--需要重复执行的任务
 8    dispatch_apply(3, applyQueue, ^(size_t index) {
 9           NSLog(@"current index %@",@(index));
10           sleep(1);
11    });
12    NSLog(@"dispatch_apply 执行完成");
13    dispatch_queue_t mainQueue = dispatch_get_main_queue();
14    dispatch_async(mainQueue, ^{
15           NSLog(@"回到主线程更新UI");
16   });
17 });
18 NSLog(@"next task");

最后是多线程单例的书写方式:(别忘了在.h中声明)

1 static MyHandle *myHandle = nil;
2 + (MyHandle *)shareMyHandle {
3     //在GCD中只执行一次,用于记录内容是否执行过
4     static dispatch_once_t onceToken;
5     dispatch_once(&onceToken, ^{
6         myHandle = [[MyHandle alloc] init];
7     });
8     return myHandle;
9 }

 

posted @ 2016-05-23 17:20  吾悠南山  阅读(276)  评论(0编辑  收藏  举报